quarkusio / quarkus

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

Kotlin no-arg/jpa compiler plugin ignored when recompiling in continuous testing mode #21427

Open languitar opened 2 years ago

languitar commented 2 years ago

Describe the bug

In a Kotlin project that uses the no-arg / jpa Kotlin compiler plugin, entities cannot be changed in continuous testing mode because the compiler plugin is not used when the affected entity class is recompiled on demand. As a result, tests the fail due to a missing no-arguments constructor.

Expected behavior

Entites can be changed just as any other code and tests simply continue to work.

Actual behavior

2021-11-13 19:43:39,882 ERROR [io.qua.test] (Test runner thread) ==================== TEST REPORT #2 ====================
2021-11-13 19:43:39,883 ERROR [io.qua.test] (Test runner thread) Test TestEntityTest#it can be persisted and retrieved() failed
: java.lang.RuntimeException: java.lang.ExceptionInInitializerError
    at io.quarkus.test.junit.QuarkusTestExtension.throwBootFailureException(QuarkusTestExtension.java:577)
    at io.quarkus.test.junit.QuarkusTestExtension.interceptTestClassConstructor(QuarkusTestExtension.java:650)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.api.extension.InvocationInterceptor.interceptTestClassConstructor(InvocationInterceptor.java:73)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:77)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestClassConstructor(ClassBasedTestDescriptor.java:355)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateTestClass(ClassBasedTestDescriptor.java:302)
    at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.instantiateTestClass(ClassTestDescriptor.java:79)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:280)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:272)
    at java.base/java.util.Optional.orElseGet(Optional.java:369)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:271)
    at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:102)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:101)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:66)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:90)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:95)
    at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:91)
    at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:60)
    at io.quarkus.deployment.dev.testing.JunitTestRunner$3.run(JunitTestRunner.java:228)
    at io.quarkus.deployment.dev.testing.ModuleTestRunner$2.run(ModuleTestRunner.java:90)
    at io.quarkus.deployment.dev.testing.TestSupport.runInternal(TestSupport.java:385)
    at io.quarkus.deployment.dev.testing.TestSupport$2.run(TestSupport.java:294)
    at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.ExceptionInInitializerError
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:398)
    at io.quarkus.runner.bootstrap.StartupActionImpl.run(StartupActionImpl.java:221)
    at io.quarkus.test.junit.QuarkusTestExtension.doJavaStart(QuarkusTestExtension.java:243)
    at io.quarkus.test.junit.QuarkusTestExtension.ensureStarted(QuarkusTestExtension.java:554)
    at io.quarkus.test.junit.QuarkusTestExtension.beforeAll(QuarkusTestExtension.java:592)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeBeforeAllCallbacks$10(ClassBasedTestDescriptor.java:381)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invoeAllCallbacks(ClassBasedTestDescriptor.java:381)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:205)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:80)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:148)
    ... 34 more
Caused by: java.lang.RuntimeException: Failed to start quarkus
    at io.quarkus.runner.ApplicationImpl.<clinit>(ApplicationImpl.zig:301)
    ... 46 more
Caused by: org.hibernate.HibernateException: Failed to generate Enhanced Proxy: default constructor is missing for entity 'TestEntity'. Please add a default constructor explicitly.
    at io.quarkus.hibernate.orm.runtime.proxies.ProxyDefinitions.createFromMetadata(ProxyDefinitions.java:75)
    at io.quarkus.hibernate.orm.runtime.boot.FastBootMetadataBuilder.build(FastBootMetadataBuilder.java:374)
    at io.quarkus.hibernate.orm.runtime.PersistenceUnitsHolder.createMetadata(PersistenceUnitsHolder.java:101)
    at io.quarkus.hibernate.orm.runtime.PersistenceUnitsHolder.constructMetadataAdvance(PersistenceUnitsHolder.java:73)
    at io.quarkus.hibernate.orm.runtime.PersistenceUnitsHolder.initializeJpa(PersistenceUnitsHolder.java:40)
    at io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder$1.created(HibernateOrmRecorder.java:68)
    at io.quarkus.arc.runtime.ArcRecorder.initBeanContainer(ArcRecorder.java:70)
    at io.quarkus.deployment.steps.ArcProcessor$generateResources-1025303321.deploy_0(ArcProcessor$generateResources-1025303321.zig:128)
    at io.quarkus.deployment.steps.ArcProcessor$generateResources-1025303321.deploy(ArcProcessor$generateResources-1025303321.zig:40)
    at io.quarkus.runner.ApplicationImpl.<clinit>(ApplicationImpl.zig:248)
    ... 46 more

How to Reproduce?

https://github.com/languitar/kotlin-panache-reload-issue contains a project to start with

  1. mvn quarkus:dev
  2. Start continuous testing by pressing r - tests will execute successfully
  3. in TestEntity.kt uncomment the line containing uncommentToTriggerBug and save the file

Tests will re-execute and fail.

Output of uname -a or ver

Linux bird 5.14.16-arch1-1 #1 SMP PREEMPT Tue, 02 Nov 2021 22:22:59 +0000 x86_64 GNU/Linux

Output of java -version

openjdk version "11.0.13" 2021-10-19

GraalVM version (if different from Java)

No response

Quarkus version or git rev

as in project

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

Maven 3.8.3

Additional information

Initially discussed in Zulip: https://quarkusio.zulipchat.com/#narrow/stream/187030-users/topic/Continuous.20testing.20with.20kotlin.20compiler.20plugins

quarkus-bot[bot] commented 2 years ago

/cc @evanchooly, @stuartwdouglas

quarkus-bot[bot] commented 2 years ago

You added a link to a Zulip discussion, please make sure the description of the issue is comprehensive and doesn't require accessing Zulip.

This message is automatically generated by a bot.

stuartwdouglas commented 2 years ago

Where does the JPA plugin come from? If you use the no-arg plugin and set <option>no-arg:annotation=javax.persistence.Entity</option> I see this working fine.

stuartwdouglas commented 2 years ago

Ok, so it looks like somehow when it sees the 'jpa' plugin it maps it to no-arg with specific parameters, it seems like we don't take these presets into account: https://github.com/JetBrains/kotlin/blob/master/plugins/noarg/noarg-cli/src/NoArgPlugin.kt#L53

stuartwdouglas commented 2 years ago

Also note that this applies to dev mode in general, its not really continuous testing related.

languitar commented 2 years ago

So, as there already is something like the jpa preset in the code, that doesn't trigger because the no-arg plugin is not in the compiler plugin list? Or what is the bug here?

stuartwdouglas commented 2 years ago

Something in our code basically does not handle presets, only the plugin + parameters form. I had a quick look but it was not really obvious what was missing. As a workaround you can use no-arg directly and just list the annotations.

@Sanne on a semi related note, what do you think about having Quarkus automatically add no-arg constructors to JPA entities if it is missing?

languitar commented 2 years ago

Thank you for the explanations! We'll try that.

Sanne commented 2 years ago

@Sanne on a semi related note, what do you think about having Quarkus automatically add no-arg constructors to JPA entities if it is missing?

I'm skeptical - if the default constructor is missing, it implies another one was added explicitly - most likely to initialize some state - which the user then expects to be initialized. I doubt people add unnecessary constructors for fun. How would you handle that?

In particular, can you guarantee we produce valid bytecode if there's final fields that require initialization?

I tend to prefer making the error explicit and let the user deal with it. Unless you have some idea to safely identify an "obvious and straight forward" case, such as someone adding a new convenience constructor - that yes could be interesting, if done carefully.

If we go to such lenght, we might be better off to generate an implementation of the Hibernate's Instantiator SPI - same effect for the user, but slightly more efficient codepath at runtime.

stuartwdouglas commented 2 years ago

The use case I was thinking of primarily was the kotlin one: https://github.com/languitar/kotlin-panache-reload-issue/blob/main/src/main/kotlin/org/acme/TestEntity.kt#L10

Also I am pretty sure there are lots of entities out there that only have a default constructor because hibernate requires it.

In terms of final fields I would just disable the feature for entities that had final fields.

Sanne commented 2 years ago

In terms of final fields I would just disable the feature for entities that had final fields.

OK yes in that case it sounds good.

MartinX3 commented 2 years ago

Still happening randomly in 2.6.2. I think if I modify a class while hotreloading and somehow the bytecode gets corrupted? Doing a gradle clean is the only way to fix it. A simply restart of the dev mode doesn't work.

geoand commented 2 years ago

@glefloch I assume this is / will be handled by the changes you made recently?

glefloch commented 2 years ago

I will give it a try and update the issue

andreas-eberle commented 1 year ago

Unfortunately, the issue still seems to exist in version 3.2.2.Final. Are there any updates? Could a new reproducer help you debug it?

mschorsch commented 9 months ago

For gradle i've found the following workaround: https://github.com/quarkusio/quarkus/issues/37109#issuecomment-1828352002. Maybe this could be a starter?

I could imagine that something like this could work for maven (untested):

<dependencies>
   <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-noarg-compiler-plugin</artifactId>
       <version>1.9.10</version>
    </dependency>
</dependencies>

<plugin>
  <groupId>${quarkus.platform.group-id}</groupId>
  <artifactId>quarkus-maven-plugin</artifactId>
  <version>${quarkus.platform.version}</version>
  <configuration>
    <compilerOptions>
      <compiler>
        <name>kotlin</name>
        <args>
          <arg>-Xplugin=${System.getProperty("user.home")/.m2/repository/org/jetbrains/kotlin/kotlin-noarg-compiler-plugin/1.9.10/kotlin-noarg-compiler-plugin-1.9.10.jar}</arg>
          <arg>-P=plugin:org.jetbrains.kotlin.noarg:preset=jpa</arg>
          <!-- <arg>-P=plugin:org.jetbrains.kotlin.noarg:annotation=com.my.Annotation</arg> -->
        </args>
      </compiler>
    </compilerOptions>
  </configuration>
</plugin>

Links: