Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error on executing tests with spring-kafka-test 3.0.0 #2490

Closed
wilsonrf opened this issue Nov 25, 2022 · 13 comments · Fixed by #2493
Closed

Error on executing tests with spring-kafka-test 3.0.0 #2490

wilsonrf opened this issue Nov 25, 2022 · 13 comments · Fixed by #2493
Assignees
Milestone

Comments

@wilsonrf
Copy link

In what version(s) of Spring for Apache Kafka are you seeing this issue?

3.0.0

Describe the bug

There's a problem executing tests when using the spring-kafka-test 3.0.0

To Reproduce

  1. Create a new codebase
  2. Run tests - ./gradlew clean build (will work fine)
  3. Add the spring-kafka-test dependency
  4. Run tests again - ./gradlew clean build
  5. Tests will fail
org.gradle.api.internal.tasks.testing.TestSuiteExecutionException: Could not complete execution for Gradle Test Executor 2.
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:63)
	at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base@17.0.3/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base@17.0.3/java.lang.reflect.Method.invoke(Method.java:568)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
	at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: java.lang.NoClassDefFoundError: org/junit/platform/launcher/TestExecutionListener
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012)
	at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
	at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
	at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:467)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.nextProviderClass(ServiceLoader.java:1217)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1228)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1273)
	at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309)
	at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393)
	at java.base/java.util.Iterator.forEachRemaining(Iterator.java:132)
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1845)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
	at org.junit.platform.launcher.core.LauncherFactory.registerTestExecutionListeners(LauncherFactory.java:179)
	at org.junit.platform.launcher.core.LauncherFactory.createDefaultLauncher(LauncherFactory.java:137)
	at org.junit.platform.launcher.core.LauncherFactory.create(LauncherFactory.java:125)
	at org.junit.platform.launcher.core.LauncherFactory.create(LauncherFactory.java:109)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:97)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	... 18 more
Caused by: java.lang.ClassNotFoundException: org.junit.platform.launcher.TestExecutionListener
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	... 50 more

Expected behavior

The tests should be executed

Sample
with spring-boot 3.0.0 https://github.com/wilsonrf/spring-kafka-test-sample
without spring-boot 3.0.0 https://github.com/wilsonrf/spring-kafka-test-sample/tree/without-springboot

@garyrussell
Copy link
Contributor

The new global embedded broker feature added a hard dependency for the platform launcher.

As a work around, please use

dependencies {
	testImplementation("org.junit.jupiter:junit-jupiter:5.9.1")
	testImplementation("org.junit.platform:junit-platform-launcher:1.9.1")
	testImplementation("org.springframework.kafka:spring-kafka-test:3.0.0")
}

With the non-boot version. With boot, you don’t need the version.

@garyrussell
Copy link
Contributor

@sbrannen Looking at the stack trace, it is clear that the launcher is on the gradle class path, but, obviously, not on the test runtime class path, while trying to load test execution listeners via the service loader.

Do you think that this is something that JUnit and/or Gradle should handle, or are we forced to make the launcher a hard transitive dependency of spring-kafka-test? The latter doesn't seem right because (perhaps rarely) not all users will use JUnit.

FWIW, I don't see the same problem with maven, and I don't see the launcher on the CP there either.

cc/ @wilkinsona

@wilkinsona
Copy link
Member

AIUI, Gradle offers better classpath separation than Maven does here. This means that the launcher isn't leaked into the classpath of the tests as it isn't typically needed. I have a feeling that TestExecutionListener may not be the right tool for the job.

@artembilan
Copy link
Member

So, there is something wrong with class loader manipulations in JUnit by itself:

	at org.junit.platform.launcher.core.LauncherFactory.registerTestExecutionListeners(LauncherFactory.java:179)
	at org.junit.platform.launcher.core.LauncherFactory.createDefaultLauncher(LauncherFactory.java:137)
	at org.junit.platform.launcher.core.LauncherFactory.create(LauncherFactory.java:125)
	at org.junit.platform.launcher.core.LauncherFactory.create(LauncherFactory.java:109)

The LauncherFactory is there and tries to load listener via service loader factory and then it fails finding its own org.junit.platform.launcher.TestExecutionListener.
Even if we can look into something else for our own solution or exclude this listener from discovery, anyone else with his/her own implementation for the TestExecutionListener would encounter the same classpath issue.
I do not see any specific note for Gradle when we develop custom TestExecutionListener in JUnit 5 docs.

I'm not sure though how then that out-of-the-box UniqueIdTrackingListener works well: it is indeed is present in the classpath same way as our GlobalEmbeddedKafkaTestExecutionListener and has its entry for service loader.

/CC @sbrannen

@garyrussell
Copy link
Contributor

@sbrannen - we need some guidance here, please; thanks!

@sbrannen
Copy link
Member

AIUI, Gradle offers better classpath separation than Maven does here. This means that the launcher isn't leaked into the classpath of the tests as it isn't typically needed.

That's correct. The JUnit Platform Launcher API is not on the test's classpath by default with Gradle, and that's by design. Users normally don't need direct access to the launcher in their test suites.

However, if that's necessary, then it's expected that the build include the junit-platform-launcher artifact in the test runtime classpath -- if the junit-platform-launcher is not automatically pulled in as a transitive dependency.

For example, adding the following to the supplied sample project allows it to build.

main branch:

testRuntimeOnly("org.junit.platform:junit-platform-launcher")

without-springboot branch:

testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.9.0")

I have a feeling that TestExecutionListener may not be the right tool for the job.

That's a bit tricky to assess.

A TestExecutionListener benefits from automatic registration; however, for Gradle the "test runtime classpath" may have to be augmented (see above).

Whereas, a global Jupiter Extension might be more suitable (in that it's limited to the Jupiter TestEngine and can interact directly with the ExtensionContext.Store), but Jupiter extensions cannot be registered automatically without setting the junit.jupiter.extensions.autodetection.enabled configuration parameter to true.

So there are pros and cons for each approach.

@sbrannen
Copy link
Member

The LauncherFactory is there and tries to load listener via service loader factory and then it fails finding its own org.junit.platform.launcher.TestExecutionListener.

I don't think that's the case. If the LauncherFactory could not see the TestExecutionListener type, there would an exception while trying to load the LauncherFactory.

Rather, I believe that the ClassLoader for the GlobalEmbeddedKafkaTestExecutionListener cannot see the TestExecutionListener, so the GlobalEmbeddedKafkaTestExecutionListener class cannot be loaded by Java's ServiceLoader mechanism (unless you add the junit-platform-launcher to the test runtime classpath).

@garyrussell
Copy link
Contributor

Thanks; I am concerned about adding it as a transitive dependency because it is possible someone might want to use the jar without any JUnit deps (I guess we could document that it can/should be excluded in that case). This might be the least path of resistance.

This is currently a big problem for anyone using initializr (with gradle as the target) because it will always fail this way, until the user adds the dep.

I would like to resolve it before Boot 3.0.1 (I have an SK 3.0.1 scheduled for that on Dec 19).

One more option might be for initializr to add the dependency only if gradle is selected, but that seems a bit smelly and it would only help boot users (although I am sure there are not many of those and only a small subset of those might not be using JUnit).

cc/ @snicoll

@artembilan
Copy link
Member

If users are going to use our spring-kafka-test without JUnit, then the mentioned org.junit.platform.launcher.core.LauncherFactory won't trigger its service loader action. Just because it is not there by build tool configuration.

@garyrussell
Copy link
Contributor

Yes; you are right, but that doesn't help with the unnecessary transitive dep for non-gradle users.

However, even now, we have a transitive dep on the jupiter API due to the condition, so maybe I am worrying too much.

We can just add the launcher as a dep and document that the two deps can be excluded if not needed because JUnit is not being used.

garyrussell added a commit to garyrussell/spring-kafka that referenced this issue Nov 30, 2022
Resolves spring-projects#2490

The new global embedded kafka broker depends on the JUnit platform launcher.
With gradle, the launcher must be on the test runtime class path.
Add the launcher as a transitive dependency.

Tested with gradle and maven Boot apps.
garyrussell added a commit to garyrussell/spring-kafka that referenced this issue Nov 30, 2022
Resolves spring-projects#2490

The new global embedded kafka broker depends on the JUnit platform launcher.
With gradle, the launcher must be on the test runtime class path.
Add the launcher as a transitive dependency.

Tested with gradle and maven Boot apps.
artembilan pushed a commit that referenced this issue Nov 30, 2022
Resolves #2490

The new global embedded kafka broker depends on the JUnit platform launcher.
With gradle, the launcher must be on the test runtime class path.
Add the launcher as a transitive dependency.

Tested with gradle and maven Boot apps.
@gurpiarbassi
Copy link
Contributor

@sbrannen Looking at the stack trace, it is clear that the launcher is on the gradle class path, but, obviously, not on the test runtime class path, while trying to load test execution listeners via the service loader.

Do you think that this is something that JUnit and/or Gradle should handle, or are we forced to make the launcher a hard transitive dependency of spring-kafka-test? The latter doesn't seem right because (perhaps rarely) not all users will use JUnit.

FWIW, I don't see the same problem with maven, and I don't see the launcher on the CP there either.

cc/ @wilkinsona

@garyrussell I have a similar problem using:

Boot: 3.0.0
spring-kafka: 3.0.0
spring-kafka-test: 3.0.0
java: 17
maven: 3.8.6
surefire: 2.22.2
failsafe: 2.22.2

My tests run fine, however I get a warning in the maven log
[WARNING] Corrupted STDOUT by directly writing to native stream in forked JVM 1. See FAQ web page and the dump file /Users/myuser/Documents/code/myservice/target/test-results/failsafe/2022-12-21T21-40-29_590-jvmRun1.dumpstream

When looking at the dump file mentioned in the warning it says:

Corrupted STDOUT by directly writing to native stream in forked JVM 1. Stream '21:40:30.521 [main] DEBUG org.springframework.kafka.test.junit.GlobalEmbeddedKafkaTestExecutionListener - JUnit Platform version must be >= 1.8 to use a global embedded kafka server'.
java.lang.IllegalArgumentException: Stream stdin corrupted. Expected comma after third character in command '21:40:30.521 [main] DEBUG org.springframework.kafka.test.junit.GlobalEmbeddedKafkaTestExecutionListener - JUnit Platform version must be >= 1.8 to use a global embedded kafka server'.
        at org.apache.maven.plugin.surefire.booterclient.output.ForkClient$OperationalData.<init>(ForkClient.java:507)
        at org.apache.maven.plugin.surefire.booterclient.output.ForkClient.processLine(ForkClient.java:210)
        at org.apache.maven.plugin.surefire.booterclient.output.ForkClient.consumeLine(ForkClient.java:177)
        at org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer$Pumper.run(ThreadedStreamConsumer.java:88)
        at java.base/java.lang.Thread.run(Thread.java:833)

I can however confirm, that the problem goes away when upgrading spring-kafka-test to 3.0.1

@bossm0n5t3r
Copy link

bossm0n5t3r commented Jan 2, 2023

I don't know if this issue is the same as what I've been through,

but when I added the library(testImplementation("org.springframework.kafka:spring-kafka-test")), the test failed due to the same error.

And when I saw this PR and removed it, it ran normally.

condition

Boot: 3.0.0
spring-kafka: 3.0.0
spring-kafka-test: 3.0.0
java: 17
Gradle: 7.6

below error is Mine

org.gradle.api.internal.tasks.testing.TestSuiteExecutionException: Could not complete execution for Gradle Test Executor 10.
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:63)
	at java.base@17.0.5/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base@17.0.5/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base@17.0.5/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base@17.0.5/java.lang.reflect.Method.invoke(Method.java:568)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
	at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: java.lang.NoClassDefFoundError: org/junit/platform/launcher/TestExecutionListener
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012)
	at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
	at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
	at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:467)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.nextProviderClass(ServiceLoader.java:1217)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1228)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1273)
	at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309)
	at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393)
	at java.base/java.util.Iterator.forEachRemaining(Iterator.java:132)
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1845)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
	at org.junit.platform.launcher.core.LauncherFactory.registerTestExecutionListeners(LauncherFactory.java:179)
	at org.junit.platform.launcher.core.LauncherFactory.createDefaultLauncher(LauncherFactory.java:137)
	at org.junit.platform.launcher.core.LauncherFactory.create(LauncherFactory.java:125)
	at org.junit.platform.launcher.core.LauncherFactory.create(LauncherFactory.java:109)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:97)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	... 18 more
Caused by: java.lang.ClassNotFoundException: org.junit.platform.launcher.TestExecutionListener
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	... 50 more

@garyrussell
Copy link
Contributor

It is the same. The work around was to add the launcher dependency, not spring-Kafka-test.

It is fixed in 3.0.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants