Skip to content

[Bug] Per-test CTest source resolution rescans every target source, freezing the extension host for minutes after each configure #5093

Description

@mewiof

Brief Issue Summary

After 1.24.42 landed our workspace became unusable in VS Code.

On window open the status bar sits on "Configuring..." for several minutes with nothing further appearing in the CMake/Build output channel, and the extension host is unresponsive the whole time (other extensions stop answering, and opening a view shows "Activating Extensions..."). Launching a target looks the same. ninja reports no work to do, the log prints

[driver] Build completed: 00:00:00.748
[build] Build finished with exit code 0

and then nothing happens for minutes before the executable starts. This is every configure and every launch, not a one-off.

The project itself is fine. The same configure takes 2.1 s from the command line, a clean 662-step build takes 28.8 s, and the target is up in 0.26 s.

What it is doing

I profiled the extension host while it was stuck. All of the time is in CTest source resolution:

36.9%  existsSync
35.4%  readFileUtf8
16.7%  readFileSync
 8.3%  parseDoctestCases                          dist/main.js:7834
 0.6%  tryResolveTestSourceFromExecutableSources  dist/main.js:7760

tryResolveTestSourceFromExecutableSources walks every source file of the test's target once per test, and tryResolveDoctestLine does existsSync + readFileSync + a regex parse for each of those files, with nothing cached between tests:

for (const source of targetInfo.sources) {
    const candidateFile = path.resolve(targetInfo.sourceDir, source);
    const candidateLine = this.tryResolveDoctestLine(candidateFile, test.name);
    if (candidateLine !== undefined) { ... }
}

We use GoogleTest through gtest_discover_tests(), which registers 4093 CTest tests, and the test target has 250 sources. Those sources contain no doctest TEST_CASEs, so parseDoctestCases never matches, the loop never exits early, and every test ends up scanning all 250 files. That is up to 1,023,250 existsSync + readFileSync + parse calls, synchronously on the extension host, per configure.

Two things make it avoidable:

The expensive path runs before the cheap one. resolveTestSourceLocation calls tryResolveTestSourceFromExecutableSources first and only checks the DEF_SOURCE_LINE test property afterwards. gtest_discover_tests already writes DEF_SOURCE_LINE for every test it registers, so the correct file and line were available without touching the disk at all.

There is also no way to opt out. cmake.ctest.testExplorerIntegrationEnabled: false gates the post-build refreshTests call (#4909, #4914), but the post-configure call is unconditional:

const result = await drv.configure(trigger, []);
if (result.exitCode === 0) { ... }
await this.cTestController.refreshTests(drv);   // not gated

cmake.configureOnOpen: false does not help either, since it still runs off the cached configuration. I measured both and neither changed the profile.

Numbers

Same workspace, same settings, only the extension version changed. CPU time measured 240 s after opening the folder, with the machine otherwise idle:

Version Extension host process All VS Code processes
1.24.42 220.0 s, still climbing (~92% of one core, indefinitely) 242.8 s
1.22.28 5.0 s, flat after 40 s 14.2 s

We have pinned 1.22.28 as a workaround, which is also why the diagnostics block below reports 1.22.28 rather than 1.24.42.

Repro

  1. A CMake project with a GoogleTest target registered through gtest_discover_tests(), where the test count multiplied by the number of sources in the test target is large. Ours is 4093 x 250, but it should be visible well below that.
  2. Build once so CTest can enumerate the tests.
  3. Open the folder with CMake Tools 1.24.42 and let it configure.
  4. The extension host pegs a core for minutes after the configure has already finished.

Suggested fix

Check DEF_SOURCE_LINE before falling back to scanning target sources, and memoize the parse per file, since the same 250 files are read and parsed 4093 times in a row. Honoring cmake.ctest.testExplorerIntegrationEnabled on the post-configure refreshTests call would at least give people a way out in the meantime.

CMake Tools Diagnostics

{
  "os": "win32",
  "vscodeVersion": "1.137.0",
  "cmtVersion": "1.22.28",
  "configurations": [
    {
      "folder": "c:\Users\mewio\Documents\GitHub\Shalltear",
      "cmakeVersion": "4.2.0",
      "configured": true,
      "generator": "Ninja",
      "usesPresets": true,
      "compilers": {
        "C": "C:/Users/mewio/Documents/GitHub/Shalltear/External/Prebuilt/llvm-mingw/bin/x86_64-w64-mingw32-clang.exe",
        "CXX": "C:/Users/mewio/Documents/GitHub/Shalltear/External/Prebuilt/llvm-mingw/bin/x86_64-w64-mingw32-clang++.exe"
      }
    }
  ],
  "cpptoolsIntegration": {
    "isReady": false,
    "hasCodeModel": false,
    "activeBuildType": "",
    "buildTypesSeen": [],
    "requests": [],
    "responses": [],
    "partialMatches": [],
    "targetCount": 0,
    "executablesCount": 0,
    "librariesCount": 0,
    "targets": []
  },
  "settings": [
    {
      "communicationMode": "automatic",
      "useCMakePresets": "auto",
      "configureOnOpen": true
    }
  ]
}

Debug Log

cmake.loggingLevel: trace, cmake.showTimestampsInOutput: true. Configure finishes at 13:38:24.481 and the last line of any kind is 13:38:25.092; the extension host then stays pegged at ~93% of one core for minutes with nothing further logged.

2026-09-13T13:38:23.441Z [info] [proc] Executing command: "C:\Program Files\CMake\bin\cmake.EXE" -DCMAKE_BUILD_TYPE=Debug -DSHALLTEAR_BUILD_SHARED=ON -DCMAKE_C_COMPILER=... -DCMAKE_CXX_COMPILER=... -DCMAKE_MAKE_PROGRAM=... -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -S C:/Users/mewio/Documents/GitHub/Shalltear -B C:/Users/mewio/Documents/GitHub/Shalltear/build -G Ninja
2026-09-13T13:38:24.478Z [trace] [cache] Read line in cache with name=GIT_EXECUTABLE-ADVANCED, typename=INTERNAL, valuestr=1
2026-09-13T13:38:24.481Z [trace] [cache] Parsed 161 cache entries
2026-09-13T13:38:24.481Z [trace] [cache] Parsed 161 entries from C:/Users/mewio/Documents/GitHub/Shalltear/build/CMakeCache.txt
2026-09-13T13:38:25.092Z [trace] [rollbar] Invoking function [$] with Rollbar wrapping [$Resolving process on "close" event]

<nothing further; UI frozen from here>

Additional Information

The profile was taken by launching VS Code with --inspect-extensions and running a CPU profile against the extension host over the DevTools protocol while the freeze was in progress, on an otherwise idle machine. The A/B was run in a separate --user-data-dir and --extensions-dir so that nothing but the extension version differed between the two runs.

Environment: VS Code 1.137.0, Windows 11 build 26200, CMake 4.2.0, Ninja, clang 23.1.1 (llvm-mingw), 28 logical cores. Test target is 250 sources and 4093 CTest tests registered by gtest_discover_tests.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    buga bug in the product

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions