From ae804857ddc2dafcd096f8bdc0f47801ac656747 Mon Sep 17 00:00:00 2001 From: Pedro Paulo Vezza Campos Date: Tue, 4 Aug 2026 11:51:08 -0700 Subject: [PATCH] test(lsp): isolate config tests from the developer's user lsp.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `lsp regressions > detects pyright and pylsp in Windows virtualenv Scripts for Python-only roots` fails on any machine that has ~/.omp/agent/lsp.json: expect(config.servers[server]?.resolvedCommand).toBe(localBin) Expected: ".../.venv/Scripts/pyright-langserver.exe" Received: undefined The test was not asserting against the packaged defaults at all. loadConfig walks the user config dirs (~/.omp/agent, ~/.pi/agent, ~/.claude) via getConfigDirPaths, which resolves from os.homedir(). Any user lsp.json with a `servers` block sets hasOverrides, which takes loadConfig off its auto-detect branch and onto the override branch, where the user's rootMarkers replace the packaged ones. On this machine that file overrides pyright with rootMarkers: ["pyproject.toml", "uv.lock", "requirements.txt", "setup.py"] which contains neither `pyrightconfig.json` nor `setup.cfg` — precisely the two markers the test creates. loadConfig therefore returned zero servers. `ruff` is absent from that file, keeps the packaged rootMarkers, and its sibling tests pass, which is why only this one failed. Confirmed by probing inside the test: hasRootMarkers() true and resolveCommand() returning the .exe, while loadConfig().servers was {} — the detection helpers were fine, the branch was not. Point os.homedir() at an empty directory for every test in the file so they see a pristine environment. Bun's os.homedir() reads the passwd entry rather than $HOME, so the env var alone does not redirect the walk; both are set. Verification: 76 pass / 0 fail (was 75 / 1) on a machine with a user lsp.json. Co-Authored-By: Claude Opus 5 --- .../test/tools/lsp-regressions.test.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/test/tools/lsp-regressions.test.ts b/packages/coding-agent/test/tools/lsp-regressions.test.ts index 9403e673b..0030b8ec0 100644 --- a/packages/coding-agent/test/tools/lsp-regressions.test.ts +++ b/packages/coding-agent/test/tools/lsp-regressions.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from "bun:test"; +import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import * as fs from "node:fs"; import * as fsp from "node:fs/promises"; import * as os from "node:os"; @@ -281,6 +281,33 @@ function textResult(result: AgentToolResult): string { .join("\n"); } +/** + * `loadConfig` walks the user config directories (~/.omp/agent, ~/.pi/agent, + * ~/.claude), which resolve from os.homedir(). A developer with a real + * lsp.json there flips loadConfig off its auto-detect path onto the override + * path, where their rootMarkers replace the packaged ones — so these tests + * would assert against that machine's config instead of the defaults. Point + * HOME at an empty directory so every case sees a pristine environment. + */ +let lspHomeOverride: string | undefined; +let lspOriginalHome: string | undefined; + +beforeEach(() => { + lspOriginalHome = process.env.HOME; + lspHomeOverride = fs.mkdtempSync(path.join(os.tmpdir(), "omp-lsp-test-home-")); + process.env.HOME = lspHomeOverride; + // Bun's os.homedir() reads the passwd entry rather than $HOME, so the env + // var alone does not redirect the config walk. + vi.spyOn(os, "homedir").mockReturnValue(lspHomeOverride); +}); + +afterEach(() => { + if (lspOriginalHome === undefined) delete process.env.HOME; + else process.env.HOME = lspOriginalHome; + if (lspHomeOverride) fs.rmSync(lspHomeOverride, { recursive: true, force: true }); + lspHomeOverride = undefined; +}); + describe("lsp regressions", () => { afterEach(() => { vi.restoreAllMocks();