Describe the bug
We are trying to implement test coverage measurement as described here: https://quarkus.io/guides/tests-with-coverage#instrumenting-the-classes-instead
For the most part this is working, but we are seeing this exception _at the end_ of the surefire/test executions in some of our Maven modules:
java.nio.channels.OverlappingFileLockException
at java.base/sun.nio.ch.FileLockTable.checkList(FileLockTable.java:229)
at java.base/sun.nio.ch.FileLockTable.add(FileLockTable.java:123)
at java.base/sun.nio.ch.FileChannelImpl.lock(FileChannelImpl.java:1109)
at java.base/java.nio.channels.FileChannel.lock(FileChannel.java:1063)
at org.jacoco.agent.rt.internal_43f5073.output.FileOutput.openFile(FileOutput.java:70)
at org.jacoco.agent.rt.internal_43f5073.output.FileOutput.writeExecutionData(FileOutput.java:54)
at org.jacoco.agent.rt.internal_43f5073.Agent.shutdown(Agent.java:145)
at org.jacoco.agent.rt.internal_43f5073.Agent$1.run(Agent.java:58)
After we stumbled upon https://github.com/jacoco/jacoco/issues/331#issuecomment-122827449, I debugged this and indeed in some cases the JaCoCo Agent ShutdownHook is registered twice, most likely because some of our instrumented classes are loaded from different classloaders (by Quarkus).
In one case the first Agent.getInstance() call stack looks like this:
Runtime.addShutdownHook(Thread) line: 213
Agent.getInstance(AgentOptions) line: 55
Offline.getRuntimeData() line: 39
Offline.getProbes(long, String, int) line: 60
SharedConfigSourceProvider.$jacocoInit(MethodHandles$Lookup, String, Class) line: not available
404458571.invokeStaticInit(Object, Object, Object, Object) line: not available
DelegatingMethodHandle$Holder.reinvoke_L(Object, Object, Object, Object) line: not available
1131710157.invoke_MT(Object, Object, Object, Object, Object) line: not available
BootstrapMethodInvoker.invoke(MethodHandle, MethodHandles$Lookup, String, Object) line: 204
BootstrapMethodInvoker.invoke(Class<T>, MethodHandle, String, Object, Object, Class<?>) line: 90
ConstantBootstraps.makeConstant(MethodHandle, String, Class<?>, Object, Class<?>) line: 67
MethodHandleNatives.linkDynamicConstantImpl(Class<?>, MethodHandle, String, Class<?>, Object) line: 314
MethodHandleNatives.linkDynamicConstant(Object, int, Object, Object, Object, Object) line: 306
SharedConfigSourceProvider.<clinit>() line: 38
NativeConstructorAccessorImpl.newInstance0(Constructor<?>, Object[]) line: not available [native method]
NativeConstructorAccessorImpl.newInstance(Object[]) line: 62
DelegatingConstructorAccessorImpl.newInstance(Object[]) line: 45
Constructor<T>.newInstance(Object...) line: 490
ServiceLoader$ProviderImpl<S>.newInstance() line: 779
ServiceLoader$ProviderImpl<S>.get() line: 721
ServiceLoader$3.next() line: 1394
ServiceLoader<S>(Iterable<T>).forEach(Consumer<? super T>) line: 74
SmallRyeConfigBuilder.discoverSources() line: 79
SmallRyeConfigBuilder.build() line: 175
ExtensionLoader.loadStepsFrom(ClassLoader, Properties, LaunchMode, Consumer<ConfigBuilder>) line: 192
ExtensionLoader.loadStepsFrom(ClassLoader, LaunchMode, Consumer<ConfigBuilder>) line: 135
QuarkusAugmentor.run() line: 101
AugmentActionImpl.runAugment(boolean, Set<String>, ClassLoader, Class<BuildItem>...) line: 245
AugmentActionImpl.createInitialRuntimeApplication() line: 130
AugmentActionImpl.createInitialRuntimeApplication() line: 52
QuarkusTestExtension.doJavaStart(ExtensionContext) line: 140
Notes:
SharedConfigSourceProvider is a custom ConfigSourceProvider we had to implement because we have multiple application*.yaml files in multiple modulesThe second Agent.getInstance() call stack looks like this:
Runtime.addShutdownHook(Thread) line: 213
Agent.getInstance(AgentOptions) line: 55
Offline.getRuntimeData() line: 39
Offline.getProbes(long, String, int) line: 60
SharedConfigSourceProvider.$jacocoInit(MethodHandles$Lookup, String, Class) line: not available
404458571.invokeStaticInit(Object, Object, Object, Object) line: not available
DelegatingMethodHandle$Holder.reinvoke_L(Object, Object, Object, Object) line: not available
1131710157.invoke_MT(Object, Object, Object, Object, Object) line: not available
BootstrapMethodInvoker.invoke(MethodHandle, MethodHandles$Lookup, String, Object) line: 204
BootstrapMethodInvoker.invoke(Class<T>, MethodHandle, String, Object, Object, Class<?>) line: 90
ConstantBootstraps.makeConstant(MethodHandle, String, Class<?>, Object, Class<?>) line: 67
MethodHandleNatives.linkDynamicConstantImpl(Class<?>, MethodHandle, String, Class<?>, Object) line: 314
MethodHandleNatives.linkDynamicConstant(Object, int, Object, Object, Object, Object) line: 306
SharedConfigSourceProvider.<clinit>() line: 38
NativeConstructorAccessorImpl.newInstance0(Constructor<?>, Object[]) line: not available [native method]
NativeConstructorAccessorImpl.newInstance(Object[]) line: 62
DelegatingConstructorAccessorImpl.newInstance(Object[]) line: 45
Constructor<T>.newInstance(Object...) line: 490
ServiceLoader$ProviderImpl<S>.newInstance() line: 779
ServiceLoader$ProviderImpl<S>.get() line: 721
ServiceLoader$3.next() line: 1394
ServiceLoader<S>(Iterable<T>).forEach(Consumer<? super T>) line: 74
SmallRyeConfigBuilder.discoverSources() line: 79
SmallRyeConfigBuilder.build() line: 175
Config.<clinit>() line: 16096
ApplicationImpl.<clinit>() line: 213
Class<T>.forName0(String, boolean, ClassLoader, Class<?>) line: not available [native method]
Class<T>.forName(String, boolean, ClassLoader) line: 398
StartupActionImpl.run(String...) line: 161
QuarkusTestExtension.doJavaStart(ExtensionContext) line: 151
It seems our SharedConfigSourceProvider is loaded a second time by a different classloader and therefore Agent.singleton is null (not initialized yet).
In this case there is only a single QuarkusTest in the respective module, no other test is present.
In another case the first Agent.getInstance() is triggered by a non-QuarkusTest (vanilla Junit5 Jupiter test) and the call stack looks like this:
Runtime.addShutdownHook(Thread) line: 213
Agent.getInstance(AgentOptions) line: 55
Offline.getRuntimeData() line: 39
Offline.getProbes(long, String, int) line: 60
ConfigurationContext.$jacocoInit(MethodHandles$Lookup, String, Class) line: not available
DirectMethodHandle$Holder.invokeStatic(Object, Object, Object, Object) line: not available
DelegatingMethodHandle$Holder.reinvoke_L(Object, Object, Object, Object) line: not available
366252104.invoke_MT(Object, Object, Object, Object, Object) line: not available
BootstrapMethodInvoker.invoke(MethodHandle, MethodHandles$Lookup, String, Object) line: 204
BootstrapMethodInvoker.invoke(Class<T>, MethodHandle, String, Object, Object, Class<?>) line: 90
ConstantBootstraps.makeConstant(MethodHandle, String, Class<?>, Object, Class<?>) line: 67
MethodHandleNatives.linkDynamicConstantImpl(Class<?>, MethodHandle, String, Class<?>, Object) line: 314
MethodHandleNatives.linkDynamicConstant(Object, int, Object, Object, Object, Object) line: 306
<unknown receiving type>(ConfigurationContext).<init>(Map<Class<Annotation>,Annotation>, Class<?>, Class<?>, String, CallModifier) line: 44
ConfigurationContextTest.doBefore() line: 34
Notes:
ConfigurationContext is one of our classesConfigurationContextTest.doBefore() is a @BeforeAll methodAfter this non-QuarkusTest there is a QuarkustTest which triggers another "initial" Agent.getInstance() invocation through ArC:
Runtime.addShutdownHook(Thread) line: 213
Agent.getInstance(AgentOptions) line: 55
Offline.getRuntimeData() line: 39
Offline.getProbes(long, String, int) line: 60
SoapClientAccessorBuilder.$jacocoInit(MethodHandles$Lookup, String, Class) line: not available
789998945.invokeStaticInit(Object, Object, Object, Object) line: not available
DelegatingMethodHandle$Holder.reinvoke_L(Object, Object, Object, Object) line: not available
366252104.invoke_MT(Object, Object, Object, Object, Object) line: not available
BootstrapMethodInvoker.invoke(MethodHandle, MethodHandles$Lookup, String, Object) line: 204
BootstrapMethodInvoker.invoke(Class<T>, MethodHandle, String, Object, Object, Class<?>) line: 90
ConstantBootstraps.makeConstant(MethodHandle, String, Class<?>, Object, Class<?>) line: 67
MethodHandleNatives.linkDynamicConstantImpl(Class<?>, MethodHandle, String, Class<?>, Object) line: 314
MethodHandleNatives.linkDynamicConstant(Object, int, Object, Object, Object, Object) line: 306
SoapClientAccessorBuilder.<clinit>() line: 85
Class<T>.forName0(String, boolean, ClassLoader, Class<?>) line: not available [native method]
Class<T>.forName(String, boolean, ClassLoader) line: 398
SoapClientAccessorBuilder_Bean.<init>() line: 543
Default_ComponentsProvider.addBeans1(Map) line: 117
Default_ComponentsProvider.getComponents() line: 38
ArcContainerImpl.<init>() line: 106
Arc.initialize() line: 20
ArcRecorder.getContainer(ShutdownContext) line: 35
ArcProcessor$generateResources11.deploy_0(StartupContext, Object[]) line: 72
ArcProcessor$generateResources11.deploy(StartupContext) line: 36
ApplicationImpl.<clinit>() line: 275
Class<T>.forName0(String, boolean, ClassLoader, Class<?>) line: not available [native method]
Class<T>.forName(String, boolean, ClassLoader) line: 398
StartupActionImpl.run(String...) line: 161
QuarkusTestExtension.doJavaStart(ExtensionContext) line: 151
Note: SoapClientAccessorBuilder is one of our classes.
This can all be debugged by adding a breakpoint to Runtime.addShutdownHook() plus mvn ... -Dmaven.surefire.debug.
Expected behavior
Quarkus classloading should not interfere with JaCoCo coverage via offline instrumentation.
Actual behavior
Duplicate JaCoCo Shutdownhook registrations seem to be caused by Quarkus classloading mechanisms, causing OverlappingFileLockExceptions to be logged that leave the user uncertain whether the coverage data can be trusted.
To Reproduce
Steps to reproduce the behavior for the ConfigSourceProvider case:
ConfigSourceProvider.ConfigSourceProvidermvn clean verify should yield:
[INFO] --- maven-surefire-plugin:2.22.1:test (integration-tests) @ tests-with-coverage-quickstart ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.acme.testcoverage.GreetingResourceTest
::: SomeConfigSourceProvider.getConfigSources() called
::: SomeConfigSourceProvider.getConfigSources() called
::: SomeConfigSourceProvider.getConfigSources() called
2020-06-07 17:20:27,662 INFO [io.quarkus] (main) Quarkus 1.5.0.Final on JVM started in 1.447s. Listening on: http://0.0.0.0:8081
2020-06-07 17:20:27,664 INFO [io.quarkus] (main) Profile test activated.
2020-06-07 17:20:27,664 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.075 s - in org.acme.testcoverage.GreetingResourceTest
2020-06-07 17:20:28,639 INFO [io.quarkus] (main) Quarkus stopped in 0.022s
java.nio.channels.OverlappingFileLockException
at java.base/sun.nio.ch.FileLockTable.checkList(FileLockTable.java:229)
at java.base/sun.nio.ch.FileLockTable.add(FileLockTable.java:123)
at java.base/sun.nio.ch.FileChannelImpl.lock(FileChannelImpl.java:1109)
at java.base/java.nio.channels.FileChannel.lock(FileChannel.java:1063)
at org.jacoco.agent.rt.internal_035b120.output.FileOutput.openFile(FileOutput.java:69)
at org.jacoco.agent.rt.internal_035b120.output.FileOutput.writeExecutionData(FileOutput.java:53)
at org.jacoco.agent.rt.internal_035b120.Agent.shutdown(Agent.java:137)
at org.jacoco.agent.rt.internal_035b120.Agent$1.run(Agent.java:54)
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
I can also try to create a reproducer for the other case, if needed.
Configuration
n/a
Environment (please complete the following information):
uname -a or ver:MINGW64_NT-10.0-18363 W4DEUMSY9003463 3.0.7-338.x86_64 2019-11-21 23:07 UTC x86_64 Msysjava -version:
openjdk version "11.0.7" 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.7+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.7+10, mixed mode)
1.4.2.Final & 1.5.0.Finalmvnw --version or gradlew --version): Apache Maven 3.6.3Additional context
This might be more of an interoperability issue between Quarkus and JaCoCo than a "classical" Quarkus-only bug, but since JaCoCo offline instumentation is "officially" documented in the Quarkus docs this should work without issues.
The classes indirectly causing the duplicate hooks can be excluded from JaCoCo instrumentation, but then they will (obviously) have 0% coverage in the report.
In one we case we did do so, though, because the "offending" class was a QuarkusTestResourceLifecycleManager from a test support module which should be ignored anyway.
cc @harthorst & @tkalmar
WDYT @stuartwdouglas (and @geoand maybe)?
I think we want the agent based instrumentation to be our default approach. I am not sure why it says that this approach is not compatible with Quarkus, its the approach we use for code coverage within Quarkus itself.
@stuartwdouglas It is because of constructor injection. The problem is described here: https://quarkus.io/guides/tests-with-coverage#the-coverage-does-not-seem-to-correspond-to-the-reality
What if Quarkus would create no-arg constructors for CDI-beans by default? cc @mkouba @manovotn
Not sure whether this would be possible.
More context:
We (~10 developers) just had an internal discussion and we would like to use _online_ instrumentation, also because that just works in IDEs whereas _offline_ instrumentation seems impossible.
But we do not want to hand-write no-arg constructors, especially since our injected members are usually final.
We also do _not_ want to go back to field injection (various reasons).
We considered https://projectlombok.org/api/lombok/NoArgsConstructor.html but we do not want to pull in Lombok just because of that and with this annotation we still would have to remember to add it to new (CDI-managed) classes.
That discussion lead to the following solutions:
@ApplicationScoped, @RequestScoped etc.Plan B (way inferior to the pervious solutions):
What if Quarkus would create no-arg constructors for CDI-beans by default? cc @mkouba @manovotn
Not sure whether this would be possible.
We already do that in some circumestances we know it's necessary
That is the problem, it means the classes loaded into memory are not the same as the ones jacoco sees on disk, and this causes problems.
We do have the ability to dump the transformed classes to disk, I wonder if jacoco could use that.
are not the same as the ones jacoco sees on disk,
AFAIK, as long as the classes already have the constructor when they are _loaded_ (from whatever source) and are processed by the JaCoCo agent, eveything is fine. But I guess this is too early for the Quarkus mechanism?
On Tue, 9 Jun 2020 at 21:24, Falko Modler notifications@github.com wrote:
are not the same as the ones jacoco sees on disk,
AFAIK, as long as the classes already have the constructor when they are
loaded (from whatever source) and are processed by the JaCoCo agent,
eveything is fine. But I guess this is too early for the Quarkus mechanism?They definitely have them when they are loaded, the agent will see the
final class file that will be provided to the JVM. It won't be the same as
the one on disk though.
Maybe it is also loading the originals, and this is confusing it.
Stuart
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/quarkusio/quarkus/issues/9837#issuecomment-641226975,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AACQG65W2YWMOWLFM5TVKT3RVYLWFANCNFSM4NVIWV3Q
.
Again AFAIK but in online mode the JaCoCo agent should not do anything with the class files.
When generating a report, though, it does need the source files. But I guess it won't fail if it doesn't find the no-arg constructor there.
cc @marchof
@famod The JaCoCo agent also transforms class files, but on-the-fly while classes are loaded in memory. During report generation the exact same original class files are required (source files are optional if you want highlighted class files). If the class files are transformed before JaCoCo agent sees them, they do not match any more. JaCoCo does not look at specific constructors. Any modification will result in a mismatch.
This is correctly described in another chapter of the document referenced earlier:
https://quarkus.io/guides/tests-with-coverage#instrumenting-the-classes-instead
@marchof thanks for clarifying this.
So how do we me move on from here?
To quote myself:
- implement custom AnnotationProcessor that adds a no-arg constructors to classes that are annotated with
@ApplicationScoped,@RequestScopedetc.
I've just learned that you cannot modify existing code with annotation processors, you can only generate new classes.
So custom bytecode manipulation is needed (in case the problem is not somehow solved in Quarkus), maybe via https://github.com/raphw/byte-buddy/tree/master/byte-buddy-maven-plugin.
One possibility would be that we could maybe write a jacoco extension that integrates with @QuarkusTest, and somehow we wire it up so it works of the transformed classes. Not sure how practical this idea is (or even if it is possible) but maybe it would also allow for the best user experience.
Update regarding _offline_ instrumentation: I've just successfully tested a fix that is supposed to land in JaCoCo 0.8.6: https://github.com/jacoco/jacoco/pull/1057
Once 0.8.6 is out I am going to update the Quarkus docs (and the example) to use 0.8.6.
Aside from that I'd still prefer a working _online_ instrumentation.
We also have that issue and apparently others. See e.g. https://quarkusio.zulipchat.com/#narrow/stream/187030-users/topic/JaCoCo.20coverage.20and.20JDK.20versions
@famod So you built JaCoCo 0.8.6 locally with that change? Or is there a snapshot version already out in some repo?
@pilhuhn I had built this branch: https://github.com/jacoco/jacoco/pull/1057
See also: https://github.com/jacoco/jacoco/issues/331#issuecomment-645503438
🎉 Update regarding online instrumentation: It seems we (or better to say @mickroll who actually implemented it) were successful in creating a plugin for byte-buddy-maven-plugin that auto-adds no-arg constructors to classes with @ApplicationScoped etc. and even add @Inject to the "proper" injection constructor.
https://gist.github.com/mickroll/1310eeed7f34a514461b25a470d434db
Please let me or @mickroll know, whether you need more info (preferably as a comment under the gist).
@stuartwdouglas WDYT, should we update the documentation to point to this issue and maybe also the JaCoCo issue?
Is there a reproducer somewhere for the agent based instrumentation issue?
@stuartwdouglas I've just modified the quickstart:
target/site/jacoco* will then not contain any coverage for GreetingResource (it does with the offline flavor).
PS: I kept the separation into UTs and ITs, which makes it a bit more complex.
So it looks like this is hard coded into the maven plugin, so we can't override it to look at the transformed classes.
The best way forward might be to create a Quarkus jacoco extension. We could perform the instrumentation using our existing transformers, and do the reporting based on the transformed classes.
Update regarding _offline_ instrumentation: I've just successfully tested a fix that is supposed to land in JaCoCo 0.8.6: jacoco/jacoco#1057
Once 0.8.6 is out I am going to update the Quarkus docs (and the example) to use 0.8.6.
Bummer, 0.8.6 has just been released _without_ this fix: https://github.com/jacoco/jacoco/issues/331#issuecomment-693211642 😞
_Update_, quote from one of the jacoco members:
There will be a release soon, for official Java 15 support, so there is another chance to get this PR in.
I had a bit of a think about how to improve this, and started some PoC work here: https://github.com/quarkusio/quarkus/compare/master...stuartwdouglas:jacoco?expand=1
My idea is to have a Jacoco extension, that will use our transformers to instrument, and then automatically generate a report once the tests are done. There are some challenges though, as there is no reliable way to automatically link to the source.
@stuartwdouglas sounds interesting!
One thing worth considering is test coverage reporting via IDE, e.g. EclEmma in Eclipse.
It would be great if that would work as well...
Btw, we have been using our ByteBuddy plugin for weeks now, without any major problems.