quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.36k stars 2.56k forks source link

When a Gradle project uses "testImplementation project(path: ':projectname', configuration: 'configurationname')" a Quarkus-vs-JVM class loader conflict appears very late in test execution #27213

Open jskillin-idt opened 1 year ago

jskillin-idt commented 1 year ago

Describe the bug

This was a particularly strange, very nasty-to-debug issue so this one may require some discussion.

First, some background: in a Maven version of a Quarkus project, there was a project structure listed here as "the easy way" to reuse some test code between projects. This influenced the project's transition to Gradle, in which the test sources of one library were wired up to be exported as a JAR under a separate configuration so that another project could depend on them and reuse them.

Gradle itself was perfectly happy with the very simplistic approach and wired up the classpath to test and run correctly.

Quarkus, however, did not appreciate these shenanigans. Very late into the test run, there were quite a lot of class loader errors, and strangely if the dependencies were tweaked, that would seemingly "fix" one error and another would appear.

Expected behavior

Quarkus is able to read the rather niche dependency list and still run.

Actual behavior

Quarkus gives a variety of stack traces fairly late in the test run, such as:

java.lang.LinkageError: loader constraint violation for class org.acme.test.EndToEnd_IT: when selecting overriding method 'org.apache.camel.builder.RouteBuilder org.acme.test.EndToEnd_IT.createRouteBuilder(java.util.Collection, java.util.Collection)' the class loader io.quarkus.bootstrap.classloading.QuarkusClassLoader @4fb392c4 of the selected method's type org.acme.test.EndToEnd_IT, and the class loader 'app' for its super type org.acme.test.AbstractEndToEnd have different Class objects for the type org.apache.camel.builder.RouteBuilder used in the signature (org.acme.test.EndToEnd_IT is in unnamed module of loader io.quarkus.bootstrap.classloading.QuarkusClassLoader @4fb392c4, parent loader io.quarkus.bootstrap.classloading.QuarkusClassLoader @3956b302; org.acme.test.AbstractEndToEnd is in unnamed module of loader 'app')

Or, in the reproducer:

java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    org/acme/ExampleTest.afterAll()V @7: invokestatic
  Reason:
    Type 'org/acme/util/SaySomethingTesty' (current frame, stack[0]) is not assignable to 'org/acme/util/SaySomething'
  Current Frame:
    bci: @7
    flags: { }
    locals: { }
    stack: { 'org/acme/util/SaySomethingTesty' }
  Bytecode:
    0000000: bb00 0b59 b700 0cb8 000d b1            

    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:398)
    at io.quarkus.test.junit.QuarkusTestExtension.runExtensionMethod(QuarkusTestExtension.java:987)
    at io.quarkus.test.junit.QuarkusTestExtension.runExtensionMethod(QuarkusTestExtension.java:977)
[snip]

How to Reproduce?

Reproducer: https://github.com/jskillin-idt/quarkusio-quarkus-issues-27213

  1. Clone the reproducer
  2. Run ./gradlew :app:test (the "extra-lib" tests will fail for inconsequential reasons)

For comparison while debugging, I've provided a version of the project which succeeds, under the branch "test-fixtures".

Output of uname -a or ver

Linux jacob-ubuntu-dev 5.15.0-43-generic #46-Ubuntu SMP Tue Jul 12 10:30:17 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

Output of java -version

openjdk version "11.0.16" 2022-07-19 OpenJDK Runtime Environment (build 11.0.16+8-post-Ubuntu-0ubuntu122.04) OpenJDK 64-Bit Server VM (build 11.0.16+8-post-Ubuntu-0ubuntu122.04, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.11.2.Final

Build tool (ie. output of mvnw --version or gradlew --version)

Gradle 7.4.1

Additional information

Gradle is a build tool that has overwhelming flexibility in what it can do, so it's not surprising to me that going off the officially-documented path discovers corner cases in plugins like Quarkus which try to wrap that complexity with their own logic and build instructions.

The particular use case that allowed me to discover this bug is invalid because an official feature called test fixtures codifies exactly what was desired, and Quarkus happens to work just fine if this feature is used instead.

However, in my humble opinion, this bug is still valid as it reveals a corner case in how dependencies are handled, and that corner case might come up again in a more obscure use case.

quarkus-bot[bot] commented 1 year ago

/cc @glefloch, @quarkusio/devtools

jskillin-idt commented 1 year ago

Reproducer has been edited in

jskillin-idt commented 1 year ago

The reproducer was updated to be far slimmer, since it was built on an old reproducer and in fact was reproducing that bug as well :laughing: The latest commits should be good to go.

jskillin-idt commented 1 year ago

(I updated the title to reflect the issue rather than what I thought was the possible solution.)