Skip to content

Searching tests cause freezes in big multi-module projects with junit 5 #1915

Closed

Description

related to #1203

You can use my open source project to reproduce it -- it's contain dozen maven modules, 2 millions loc, 9000 junit4 and junit5 tests, and already preconfigured and optimized with dev container (vscode and java tools inside, it also include instruction to setup and first run command):

Main problem:

  • if you run tests from the root module (mage-tests) -- you will see popup "searching tests" message -- it's take 100-150+ seconds on any runs (cold and hot);
  • single tests or small "folders" starting fast;

Dump collect by command (call it while "searching tests"):

  • jstack $(pgrep -f equinox.launcher) > jdtls-hang.txt

After some dumps research it was found the root of the problem: JUnit5TestFinder process. It's try to scan and construct full AST tree and fail on big amount of files. JUnit4TestFinder works too fast and miss from the dumps.

JUnit5TestFinder.internalIsTest()

WeakHashSet.cleanupGarbageCollectedValues
→ WeakHashSet.add
→ DeduplicationUtil.toString (locked WeakHashSet)
→ SearchableEnvironment.findType
→ ...

dump 1 example:

"ForkJoinPool.commonPool-worker-5" #337 [2445] daemon prio=5 os_prio=0 cpu=22747.59ms elapsed=184.51s tid=0x0000ffff140010f0 nid=2445 runnable
   java.lang.Thread.State: RUNNABLE
	at org.eclipse.jdt.internal.compiler.parser.Scanner.jumpOverMethodBody(Scanner.java:2247)
	at org.eclipse.jdt.internal.compiler.parser.Scanner.getNextToken0(Scanner.java:1286)
	at org.eclipse.jdt.internal.compiler.parser.Parser.dietParse(Parser.java:10308)
	at org.eclipse.jdt.internal.compiler.Compiler.accept(Compiler.java:220)
	at org.eclipse.jdt.core.dom.CompilationUnitResolver.accept(CompilationUnitResolver.java:230)
	- locked <0x00000007aadbb700> (a org.eclipse.jdt.core.dom.CompilationUnitResolver)
	at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:434)
	...
	at org.eclipse.jdt.internal.compiler.lookup.ClassScope.connectTypeHierarchy(ClassScope.java:1394)
	at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.completeTypeBindings(LookupEnvironment.java:612)
	at org.eclipse.jdt.internal.compiler.Compiler.internalBeginToCompile(Compiler.java:787)
	at org.eclipse.jdt.core.dom.CompilationUnitResolver.resolve(CompilationUnitResolver.java:1240)
	at org.eclipse.jdt.core.dom.ASTParser.internalCreateASTCached(ASTParser.java:1412)
	at org.eclipse.jdt.core.dom.ASTParser.createAST(ASTParser.java:933)
	at org.eclipse.jdt.internal.junit.launcher.JUnit5TestFinder.internalIsTest(JUnit5TestFinder.java:271)
	at org.eclipse.jdt.internal.junit.launcher.JUnit5TestFinder.findTestsInContainer(JUnit5TestFinder.java:221)
	at com.microsoft.java.test.plugin.searcher.JUnit5TestSearcher.findTestItemsInContainer(JUnit5TestSearcher.java:130)
	at com.microsoft.java.test.plugin.util.TestSearchUtils.findTestPackagesAndTypes(TestSearchUtils.java:169)
	at com.microsoft.java.test.plugin.handler.TestDelegateCommandHandler.executeCommand(TestDelegateCommandHandler.java:61)

dump 2 example:

"ForkJoinPool.commonPool-worker-8" #340 [2448] daemon prio=5 os_prio=0 cpu=22085.22ms elapsed=184.51s tid=0x0000fffeec001b30 nid=2448 runnable
   java.lang.Thread.State: RUNNABLE
	at org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser.commentParse(AbstractCommentParser.java:225)
	at org.eclipse.jdt.internal.compiler.parser.JavadocParser.checkDeprecation(JavadocParser.java:117)
	...
	at org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding.methods(SourceTypeBinding.java:1770)
	at org.eclipse.jdt.internal.compiler.lookup.MethodVerifier.computeInheritedMethods(MethodVerifier.java:543)
	at org.eclipse.jdt.internal.compiler.lookup.MethodVerifier.verify(MethodVerifier.java:1012)
	at org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding.verifyMethods(SourceTypeBinding.java:2830)
	at org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope.verifyMethods(CompilationUnitScope.java:1025)
	at org.eclipse.jdt.core.dom.CompilationUnitResolver.resolve(CompilationUnitResolver.java:1307)
	at org.eclipse.jdt.core.dom.ASTParser.createAST(ASTParser.java:933)
	at org.eclipse.jdt.internal.junit.launcher.JUnit5TestFinder.internalIsTest(JUnit5TestFinder.java:271)
	at org.eclipse.jdt.internal.junit.launcher.JUnit5TestFinder.findTestsInContainer(JUnit5TestFinder.java:221)
	at com.microsoft.java.test.plugin.searcher.JUnit5TestSearcher.findTestItemsInContainer(JUnit5TestSearcher.java:130)
	at com.microsoft.java.test.plugin.util.TestSearchUtils.findTestPackagesAndTypes(TestSearchUtils.java:169)
	at com.microsoft.java.test.plugin.handler.TestDelegateCommandHandler.executeCommand(TestDelegateCommandHandler.java:61)

dump 3 example

"ForkJoinPool.commonPool-worker-5" #337 [2445] daemon prio=5 os_prio=0 cpu=52162.83ms elapsed=609.17s tid=0x0000ffff140010f0 nid=2445 runnable
   java.lang.Thread.State: RUNNABLE
	at org.eclipse.jdt.internal.core.util.WeakHashSet.cleanupGarbageCollectedValues(WeakHashSet.java:123)
	at org.eclipse.jdt.internal.core.util.WeakHashSet.add(WeakHashSet.java:78)
	at org.eclipse.jdt.internal.core.util.DeduplicationUtil.toString(DeduplicationUtil.java:46)
	- locked <0x000000070209e980> (a org.eclipse.jdt.internal.core.util.WeakHashSet)
	at org.eclipse.jdt.internal.core.SearchableEnvironment.findType(SearchableEnvironment.java:576)
	at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:386)
	at org.eclipse.jdt.internal.compiler.lookup.MethodVerifier.computeInheritedMethods(MethodVerifier.java:543)
	at org.eclipse.jdt.internal.compiler.lookup.MethodVerifier.verify(MethodVerifier.java:1012)
	at org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding.verifyMethods(SourceTypeBinding.java:2830)
	at org.eclipse.jdt.core.dom.CompilationUnitResolver.resolve(CompilationUnitResolver.java:1307)
	at org.eclipse.jdt.core.dom.ASTParser.createAST(ASTParser.java:933)
	at org.eclipse.jdt.internal.junit.launcher.JUnit5TestFinder.internalIsTest(JUnit5TestFinder.java:271)
	at org.eclipse.jdt.internal.junit.launcher.JUnit5TestFinder.findTestsInContainer(JUnit5TestFinder.java:221)
	at com.microsoft.java.test.plugin.searcher.JUnit5TestSearcher.findTestItemsInContainer(JUnit5TestSearcher.java:130)
	at com.microsoft.java.test.plugin.util.TestSearchUtils.findTestPackagesAndTypes(TestSearchUtils.java:169)

Activity

  1. changed the title [-]Searching tests cause freezes in big multi-modal projects with junit 5[/-] [+]Searching tests cause freezes in big multi-module projects with junit 5[/+] on Aug 22, 2026
  2. github-actions commented on Aug 22, 2026

    @github-actions

    Hi Oleg Agafonov (@JayDi85), I'm an AI Support assistant here to help with your issue. While the team reviews your request, I wanted to provide some possible tips and documentation that might help you in the meantime.

    Suggestions

    • Scope test discovery to specific modules or folders:

      • Right-click the desired module (e.g., mage-tests) in the Test Explorer and select Run All Tests, instead of running from the root workspace. This limits the search to that module’s test classes.
      • Open only the module you’re actively working on as a standalone workspace. This avoids scanning all 2 million LOC at once.
    • Exclude large or irrelevant test packages via VS Code settings (in .vscode/settings.json):

      {
        "java.test.fileExcludes": [
          "**/some-large-module/**"
        ]
      }

      Adjust the glob patterns to skip directories that don’t need to be scanned.

    • Track the underlying performance issue on the VS Code Java Test extension:

    Other references with low confidence

    The team will respond to your issue shortly. I hope these suggestions are helpful in the meantime. If this comment helped you, please give it a 👍. If the suggestion was not helpful or incorrect, please give it a 👎. Your feedback helps us improve!

  3. wenytang-ms commented on Aug 24, 2026

    @wenytang-ms
    Contributor

    Thanks for the detailed report and the public reproduction project.

    I was able to reproduce the prolonged project-level "Searching tests..." behavior and reviewed the supplied thread dumps. The sampled threads are RUNNABLE and repeatedly show work under JUnit5TestFinder.internalIsTest() and binding-resolved AST creation, so the dumps do not show a deadlock. However, the complete "Searching tests..." phase also includes additional work in vscode-java-test, so the entire delay should not be attributed to JUnit5TestFinder based on the current data.

    There are also potential improvement areas in vscode-java-test's current discovery flow:

    • A project-level run re-resolves the project's test items even when they have already been loaded.
    • After class discovery, the extension expands the discovered classes to method level, which can introduce additional language-server requests and AST parsing.

    These extension-side behaviors are particularly relevant when comparing initial and repeated project-level runs. This issue is an appropriate place to continue investigating that part of the discovery flow.

    For the JDT/JUnit discovery path observed in the thread dumps, I opened eclipse-jdtls/eclipse.jdt.ls#3870 with the pinned reproduction revision and the corresponding code observations. The server-side invocation originates from vscode-java-test through:

    TestDelegateCommandHandler
      -> TestSearchUtils.findTestPackagesAndTypes
      -> JUnit5TestSearcher.findTestItemsInContainer
      -> JUnit5TestFinder.findTestsInContainer
    

    That issue focuses on the cost observed during a single JUnit 5 finder invocation and on determining the appropriate layer for any finder-level change. The repeated project resolution and subsequent method-level expansion remain specific to vscode-java-test and are tracked here.

    One correction to the automated suggestion above: java.test.fileExcludes is not a supported Test Runner for Java setting, so that workaround should be disregarded.

  4. wenytang-ms commented on Sep 21, 2026

    @wenytang-ms
    Contributor

    Thanks again for the detailed report and the public reproduction project.

    The vscode-java-test part of this issue has been addressed by #1916. Resolved Project and Class test items are now reused, concurrent resolutions are coalesced, and the cached state is invalidated when a refresh or classpath change requires discovery to run again. In the magefree/mage validation, an already-resolved project expanded in about 2.4 seconds instead of repeating the roughly 163-second discovery.

    The remaining cost is the initial JUnit 5 discovery performed in JDT/JDT LS. That belongs to the JDT scope and is tracked separately in eclipse-jdtls/eclipse.jdt.ls#3870.

    Closing this issue as completed for the vscode-java-test scope.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions