Skip to content

Commit 0ed6470

Browse files
committed
Trigger the ripgrep timeout test deterministically
The test raced a real spawned ripgrep process against a 1ms timeout, so it depended on the process being slower than the clock rather than on the timeout path itself. Thread the same stalled-child fake already used at the runRg level through ripgrepPlugin so the timeout is the only path to settlement.
1 parent 5426034 commit 0ed6470

2 files changed

Lines changed: 18 additions & 4 deletions

File tree

src/plugins/ripgrep-plugin.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
type BoundedGrepArgs,
99
} from "./bounded-grep-fallback.js";
1010
import { createRgCollector } from "./rg-output.js";
11-
import { MAX_OUTPUT_BYTES, runRg, type RgLimits } from "./rg-run.js";
11+
import { MAX_OUTPUT_BYTES, runRg, type RgLimits, type SpawnRg } from "./rg-run.js";
1212

1313
// A grep over a large tree with the pure-TypeScript walker enumerates the whole
1414
// directory (node_modules, build output, the lot) before searching, which stalls
@@ -66,7 +66,7 @@ function searchLocation(path: string, fallbackCwd: string): { cwd: string; targe
6666
}
6767
}
6868

69-
export function ripgrepPlugin(cwd: string, limits: RgLimits = {}): ToolPlugin {
69+
export function ripgrepPlugin(cwd: string, limits: RgLimits = {}, spawnChild?: SpawnRg): ToolPlugin {
7070
const maxBytes = limits.maxOutputBytes ?? MAX_OUTPUT_BYTES;
7171
return {
7272
middleware: (next) => async (call, signal) => {
@@ -86,7 +86,7 @@ export function ripgrepPlugin(cwd: string, limits: RgLimits = {}): ToolPlugin {
8686
if (glob !== undefined) rgArgs.push("-g", glob);
8787
rgArgs.push("--regexp", pattern, target);
8888

89-
const result = await runRg(rgArgs, rgCwd, signal, limits);
89+
const result = await runRg(rgArgs, rgCwd, signal, limits, spawnChild);
9090
if (result.kind === "unavailable") {
9191
try {
9292
const boundedArgs: BoundedGrepArgs = {

tests/unit/ripgrep-plugin.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { tmpdir } from "node:os";
55
import type { ToolCall, ToolResult } from "@intx/types/runtime";
66

77
import { ripgrepPlugin } from "../../src/plugins/ripgrep-plugin.js";
8+
import type { RgChild, SpawnRg } from "../../src/plugins/rg-run.js";
89

910
// Repo root derived from this file, not process.cwd(): these cases search real
1011
// repo paths, so they must not depend on where the runner was invoked from.
@@ -14,11 +15,23 @@ const fallback = async (): Promise<ToolResult> => ({ callId: "c", content: "FALL
1415
function run(
1516
call: ToolCall,
1617
limits: { timeoutMs?: number; maxOutputBytes?: number } = {},
18+
spawnChild?: SpawnRg,
1719
): Promise<ToolResult> {
18-
const handler = ripgrepPlugin(cwd, limits).middleware!(fallback);
20+
const handler = ripgrepPlugin(cwd, limits, spawnChild).middleware!(fallback);
1921
return handler(call, new AbortController().signal);
2022
}
2123

24+
// A child that never emits data or closes, so the timeout is the only path
25+
// to settlement — the trigger the timeout test needs, not a race against how
26+
// fast a real ripgrep process happens to run.
27+
const stalledSpawn: SpawnRg = (): RgChild => ({
28+
pid: undefined,
29+
stdout: { on: () => undefined },
30+
stderr: { on: () => undefined },
31+
on: (() => undefined) as RgChild["on"],
32+
kill: () => undefined,
33+
});
34+
2235
async function withTempDir(run: (dir: string) => Promise<void>): Promise<void> {
2336
const dir = await mkdtemp(join(tmpdir(), "ripgrep-plugin-"));
2437
try {
@@ -114,6 +127,7 @@ test("grep returns partial matches when the timeout fires", async () => {
114127
const result = await run(
115128
{ id: "c", name: "grep", arguments: { pattern: "e", path: "src" } },
116129
{ timeoutMs: 1 },
130+
stalledSpawn,
117131
);
118132
expect(result.isError).toBeUndefined();
119133
expect(result.content).toContain("timed out");

0 commit comments

Comments
 (0)