Repository navigation
FileSystemAlreadyExistsException when build tool uses resources in JARs rather than plain files #6080
Description
The original context is a Scala project (build tool SBT) where following the upgrade to SBT 2, I faced this error intermittently:
Details
WARNING: Error scanning files for URI jar:file:/home/runner/.cache/sbt/v2/cas/sha256-6a387a1afa16efdfb8dc71a8b673f22e5cb2115c97cbe8b1a89a399ce004014a-214786!/parametertypes
java.nio.file.FileSystemAlreadyExistsException
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:101)
at java.base/java.nio.file.FileSystems.newFileSystem(FileSystems.java:322)
at java.base/java.nio.file.FileSystems.newFileSystem(FileSystems.java:273)
at org.junit.platform.commons.util.CloseablePath.lambda$create$0(CloseablePath.java:52)
at org.junit.platform.commons.util.CloseablePath$ManagedFileSystem.<init>(CloseablePath.java:106)
at org.junit.platform.commons.util.CloseablePath.lambda$createForJarFileSystem$0(CloseablePath.java:75)
at java.base/java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1932)
at org.junit.platform.commons.util.CloseablePath.createForJarFileSystem(CloseablePath.java:74)
at org.junit.platform.commons.util.CloseablePath.create(CloseablePath.java:62)
at org.junit.platform.commons.util.CloseablePath.create(CloseablePath.java:52)
at org.junit.platform.commons.util.DefaultClasspathScanner.walkFilesForUri(DefaultClasspathScanner.java:167)
at org.junit.platform.commons.util.DefaultClasspathScanner.findResourcesForUri(DefaultClasspathScanner.java:159)
at org.junit.platform.commons.util.DefaultClasspathScanner.lambda$findResourcesForUris$0(DefaultClasspathScanner.java:149)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1716)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:635)
at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:291)
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:652)
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:658)
at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:663)
at org.junit.platform.commons.util.DefaultClasspathScanner.findResourcesForUris(DefaultClasspathScanner.java:152)
at org.junit.platform.commons.util.DefaultClasspathScanner.scanForResourcesInPackage(DefaultClasspathScanner.java:108)
at org.junit.platform.commons.util.ReflectionUtils.findAllResourcesInPackage(ReflectionUtils.java:1036)
at org.junit.platform.commons.support.ResourceSupport.findAllResourcesInPackage(ResourceSupport.java:152)
at org.junit.platform.engine.support.discovery.ResourceContainerSelectorResolver.resolve(ResourceContainerSelectorResolver.java:61)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.lambda$resolve$0(EngineDiscoveryRequestResolution.java:161)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1693)
at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:147)
at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:588)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:574)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:683)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.resolve(EngineDiscoveryRequestResolution.java:191)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.resolve(EngineDiscoveryRequestResolution.java:128)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.resolveCompletely(EngineDiscoveryRequestResolution.java:94)
[error] Test parametertypes.RunParameterTypesTest failed: org.junit.platform.suite.engine.NoTestsDiscoveredException: Suite [parametertypes.RunParameterTypesTest] did not discover any tests, took 0.006s
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.run(EngineDiscoveryRequestResolution.java:85)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver.resolve(EngineDiscoveryRequestResolver.java:150)
at io.cucumber.junit.platform.engine.DiscoverySelectorResolver.resolveSelectors(DiscoverySelectorResolver.java:32)
at io.cucumber.junit.platform.engine.FeaturesPropertyResolver.resolveSelectors(FeaturesPropertyResolver.java:52)
at io.cucumber.junit.platform.engine.CucumberTestEngine.discover(CucumberTestEngine.java:65)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverEngineRoot(EngineDiscoveryOrchestrator.java:195)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverSafely(EngineDiscoveryOrchestrator.java:174)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discover(EngineDiscoveryOrchestrator.java:119)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discover(EngineDiscoveryOrchestrator.java:102)
at org.junit.platform.suite.engine.SuiteLauncher.discover(SuiteLauncher.java:58)
at org.junit.platform.suite.engine.SuiteTestDescriptor.discover(SuiteTestDescriptor.java:133)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:186)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:153)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:176)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:632)
at org.junit.platform.suite.engine.DiscoverySelectorResolver.discoverSuites(DiscoverySelectorResolver.java:40)
at org.junit.platform.suite.engine.DiscoverySelectorResolver.resolveSelectors(DiscoverySelectorResolver.java:48)
at org.junit.platform.suite.engine.SuiteTestEngine.discover(SuiteTestEngine.java:62)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverEngineRoot(EngineDiscoveryOrchestrator.java:195)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverSafely(EngineDiscoveryOrchestrator.java:174)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discover(EngineDiscoveryOrchestrator.java:119)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discover(EngineDiscoveryOrchestrator.java:84)
at org.junit.platform.launcher.core.DefaultLauncher.discover(DefaultLauncher.java:131)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$1(DefaultLauncher.java:124)
at java.base/java.util.Optional.orElseGet(Optional.java:364)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:121)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:100)
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:57)
at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$0(InterceptingLauncher.java:46)
at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25)
at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:45)
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:57)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:76)
at com.github.sbt.junit.jupiter.internal.JupiterRunner$JupiterTaskExecutor.execute(JupiterRunner.java:227)
at com.github.sbt.junit.jupiter.internal.JupiterRunner$WithCustomProperties.execute(JupiterRunner.java:300)
at com.github.sbt.junit.jupiter.internal.JupiterRunner$JupiterTask.execute(JupiterRunner.java:131)
at sbt.TestRunner.runTest$1(TestFramework.scala:154)
at sbt.TestRunner.run(TestFramework.scala:170)
at sbt.TestFramework$.$anon$superArg$1$1$$anonfun$1$$anonfun$1(TestFramework.scala:317)
at sbt.TestFramework$.withContextLoader(TestFramework.scala:303)
at sbt.TestFramework$.$anon$superArg$1$1$$anonfun$1(TestFramework.scala:317)
at sbt.TestFunction.apply(TestFramework.scala:329)
at sbt.Tests$.$anonfun$8(Tests.scala:440)
at sbt.std.Transform$.sbt$std$Transform$$anon$1$$_$apply$$anonfun$1(Transform.scala:50)
at sbt.std.Transform$$anon$2.work(Transform.scala:70)
at sbt.std.Transform$$anon$2.work(Transform.scala:70)
at sbt.Execute.submit$$anonfun$1$$anonfun$1(Execute.scala:283)
at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:24)
at sbt.Execute.work(Execute.scala:294)
at sbt.Execute.submit$$anonfun$1(Execute.scala:283)
at sbt.ConcurrentRestrictions$$anon$4.$anonfun$2(ConcurrentRestrictions.scala:269)
at sbt.CompletionService$$anon$2.call(CompletionService.scala:75)
at sbt.CompletionService$$anon$2.call(CompletionService.scala:73)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
at java.base/java.lang.Thread.run(Thread.java:1474)
Related run and code: https://github.com/cucumber/cucumber-jvm-scala/actions/runs/32169643804/job/95817434873?pr=434
Originally reported to SBT team (sbt/sbt#9625) but they claim it's not a SBT issue but rather a JUnit one.
I believe the underlying root cause is that SBT 2 changed the way resources (and classes) are given to the test classpath: they are now provided in JARs (from ~/.cache/sbt/v2/cas/, as can be seen in the stacktrace) rather than plain files (like SBT 1.x).
When tests are run concurrently and access the same JAR, the exception above can be raised.
I spent a bit of time to build a reproduction repository, using SBT and Scala: https://github.com/gaeljw/sbt2-junit5-fsrace-repro. It was built with AI but I reviewed it and it's close to the actual call path I was hitting in my original project (cucumber-jvm-scala).
The high-level idea is that several "runners" @Suite (concurrently) have to scan the classpath for @Tests and when these tests are in a JAR file in the classpath, the FileSystemAlreadyExistsException can be hit. The exception is logged as a WARNING but has then for consequence that the runner fails with org.junit.platform.suite.engine.NoTestsDiscoveredException: Suite [consumertest.RunSharedTests] did not discover any tests.
Stack trace in the reproduction repo:
Details
Error scanning files for URI jar:file:/home/jourdang/.cache/sbt/v2/cas/sha256-a8940a97428e0c9599333c3a98649948f6ec3f4d619e9d7aa456474806b9c8ce-506072!/shared
java.nio.file.FileSystemAlreadyExistsException
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:101)
at java.base/java.nio.file.FileSystems.newFileSystem(FileSystems.java:322)
at java.base/java.nio.file.FileSystems.newFileSystem(FileSystems.java:273)
at org.junit.platform.commons.util.CloseablePath.lambda$create$0(CloseablePath.java:52)
at org.junit.platform.commons.util.CloseablePath$ManagedFileSystem.<init>(CloseablePath.java:106)
at org.junit.platform.commons.util.CloseablePath.lambda$createForJarFileSystem$0(CloseablePath.java:75)
at java.base/java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1932)
at org.junit.platform.commons.util.CloseablePath.createForJarFileSystem(CloseablePath.java:74)
at org.junit.platform.commons.util.CloseablePath.create(CloseablePath.java:62)
at org.junit.platform.commons.util.CloseablePath.create(CloseablePath.java:52)
at org.junit.platform.commons.util.DefaultClasspathScanner.walkFilesForUri(DefaultClasspathScanner.java:167)
at org.junit.platform.commons.util.DefaultClasspathScanner.findClassesForUri(DefaultClasspathScanner.java:135)
at org.junit.platform.commons.util.DefaultClasspathScanner.lambda$findClassesForUris$0(DefaultClasspathScanner.java:125)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1716)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:635)
at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:291)
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:652)
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:658)
at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:663)
at org.junit.platform.commons.util.DefaultClasspathScanner.findClassesForUris(DefaultClasspathScanner.java:128)
at org.junit.platform.commons.util.DefaultClasspathScanner.scanForClassesInPackage(DefaultClasspathScanner.java:88)
at org.junit.platform.commons.util.ReflectionUtils.findAllClassesInPackage(ReflectionUtils.java:1029)
at org.junit.platform.commons.util.ReflectionUtils.findAllClassesInPackage(ReflectionUtils.java:1013)
at org.junit.platform.commons.support.ReflectionSupport.findAllClassesInPackage(ReflectionSupport.java:282)
at org.junit.platform.engine.support.discovery.ClassContainerSelectorResolver.resolve(ClassContainerSelectorResolver.java:58)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.lambda$resolve$0(EngineDiscoveryRequestResolution.java:161)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1693)
at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:147)
at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:588)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:574)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:683)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.resolve(EngineDiscoveryRequestResolution.java:191)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.resolve(EngineDiscoveryRequestResolution.java:128)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.resolveCompletely(EngineDiscoveryRequestResolution.java:94)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.run(EngineDiscoveryRequestResolution.java:85)
at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver.resolve(EngineDiscoveryRequestResolver.java:150)
at org.junit.platform.suite.engine.DiscoverySelectorResolver.resolveSelectors(DiscoverySelectorResolver.java:47)
at org.junit.platform.suite.engine.SuiteTestEngine.discover(SuiteTestEngine.java:62)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverEngineRoot(EngineDiscoveryOrchestrator.java:195)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverSafely(EngineDiscoveryOrchestrator.java:174)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discover(EngineDiscoveryOrchestrator.java:119)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discover(EngineDiscoveryOrchestrator.java:102)
at org.junit.platform.suite.engine.SuiteLauncher.discover(SuiteLauncher.java:58)
at org.junit.platform.suite.engine.SuiteTestDescriptor.discover(SuiteTestDescriptor.java:133)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:186)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:153)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:176)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:632)
at org.junit.platform.suite.engine.DiscoverySelectorResolver.discoverSuites(DiscoverySelectorResolver.java:40)
at org.junit.platform.suite.engine.DiscoverySelectorResolver.resolveSelectors(DiscoverySelectorResolver.java:48)
at org.junit.platform.suite.engine.SuiteTestEngine.discover(SuiteTestEngine.java:62)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverEngineRoot(EngineDiscoveryOrchestrator.java:195)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverSafely(EngineDiscoveryOrchestrator.java:174)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discover(EngineDiscoveryOrchestrator.java:119)
at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discover(EngineDiscoveryOrchestrator.java:84)
at org.junit.platform.launcher.core.DefaultLauncher.discover(DefaultLauncher.java:131)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$1(DefaultLauncher.java:124)
at java.base/java.util.Optional.orElseGet(Optional.java:364)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:121)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:100)
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:57)
at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$0(InterceptingLauncher.java:46)
at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25)
at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:45)
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:57)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:76)
at com.github.sbt.junit.jupiter.internal.JupiterRunner$JupiterTaskExecutor.execute(JupiterRunner.java:227)
at com.github.sbt.junit.jupiter.internal.JupiterRunner$WithCustomProperties.execute(JupiterRunner.java:300)
at com.github.sbt.junit.jupiter.internal.JupiterRunner$JupiterTask.execute(JupiterRunner.java:131)
at sbt.TestRunner.runTest$1(TestFramework.scala:154)
at sbt.TestRunner.run(TestFramework.scala:170)
at sbt.TestFramework$.$anon$superArg$1$1$$anonfun$1$$anonfun$1(TestFramework.scala:317)
at sbt.TestFramework$.withContextLoader(TestFramework.scala:303)
at sbt.TestFramework$.$anon$superArg$1$1$$anonfun$1(TestFramework.scala:317)
at sbt.TestFunction.apply(TestFramework.scala:329)
at sbt.Tests$.$anonfun$8(Tests.scala:440)
at sbt.std.Transform$.sbt$std$Transform$$anon$1$$_$apply$$anonfun$1(Transform.scala:50)
at sbt.std.Transform$$anon$2.work(Transform.scala:70)
at sbt.std.Transform$$anon$2.work(Transform.scala:70)
at sbt.Execute.submit$$anonfun$1$$anonfun$1(Execute.scala:283)
at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:24)
at sbt.Execute.work(Execute.scala:294)
at sbt.Execute.submit$$anonfun$1(Execute.scala:283)
at sbt.ConcurrentRestrictions$$anon$4.$anonfun$2(ConcurrentRestrictions.scala:269)
at sbt.CompletionService$$anon$2.call(CompletionService.scala:75)
at sbt.CompletionService$$anon$2.call(CompletionService.scala:73)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
at java.base/java.lang.Thread.run(Thread.java:1474)
I can't definitely say if it's a JUnit issue but I wanted to raise it to you as you will likely understand this better than I do. Any feedback appreciated.
Sounds related to:
Activity
mpkorstanje commentedon Sep 20, 2026
This is a bit of a slop grenade.
Could you fact check these findings to the point you no longer have to attribute them to Claude? I'm going to assume the reproducer is based on the same findings, but without you confirming them the findings are purely hypothetical.
More importantly, it would be useful to know how SBT invokes JUnit and Cucumber otherwise we will indeed end up having to debug SBT. And for that a minimal reproducer with SBT would have been useful. 😉
gaeljw commentedon Sep 20, 2026
Hey, nice to see you here @mpkorstanje :)
That's fair feedback.
I do confirm the fact that SBT 2 changed its behavior regarding putting resources in JARs rather than plain files in the test classpath.
I spent a bit of time to build a closer reproduction repository, using SBT this time: https://github.com/gaeljw/sbt2-junit5-fsrace-repro. It was built with AI but I reviewed it and it's close to the actual call path I was hitting in my original project (cucumber-jvm-scala).
The high-level idea is that several "runners"
@Suite(concurrently) have to scan the classpath for@Tests and when these tests are in a JAR file in the classpath, theFileSystemAlreadyExistsExceptioncan be hit. The exception is logged as aWARNINGbut has then for consequence that the runner fails withorg.junit.platform.suite.engine.NoTestsDiscoveredException: Suite [consumertest.RunSharedTests] did not discover any tests.Stack trace in the reproduction repo:
(Original post updated as well)
marcphilipp commentedon Sep 20, 2026
@gaeljw Could you please try this workaround?
mpkorstanje commentedon Sep 20, 2026
Small world 😄
Much appreciated!
mpkorstanje commentedon Sep 20, 2026
If that works it makes me wonder who's bug this is. To discover tests the JUnit Platform needs to be able to scan the classpath. And it needs to be able to do so in parallel if SBT runs JUnit in parallel. But that requires book keeping to keep track of the open file systems, which SBT also prevents by putting the concurrent instances of JUnit in separate class loaders.
With those constraints I don't think anyone can reasonably do anything with any shared resource.
6 remaining items
mpkorstanje commentedon Sep 25, 2026
[-][Platform] Intermittent FileSystemAlreadyExistsException when build tool uses resources in JARs rather than plain files[/-][+]FileSystemAlreadyExistsException when build tool uses resources in JARs rather than plain files[/+]mpkorstanje commentedon Oct 21, 2026
So in the end it turns out that
FileSystems.newFileSystem(Path, Map)behaves rather differently thanFileSystems.newFileSystem(URI, Map). For the former theZipFileSystemProviderwill always open a new file system, the latter will cache the opened file system and refuse to open another when already open.Reproducer in JShell:
We're reworking the logic to use
FileSystems.newFileSystem(Path, Map)in #6143.gaeljw commentedon Oct 21, 2026
Interesting! It's always "funny" to discover such behaviour differences from 2 apparently very similar methods 🤯
Create separate file systems for jars during classpath scanning (#6143)
github-actions commentedon Oct 23, 2026
Please assign a milestone to this issue or label it with
type: taskortype: question– or assign a status label and close it as not planned.