Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/search-engines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ function candidates(engine: Engine): string[] {
function verify(binary: string): Promise<boolean> {
return new Promise((resolve) => {
let settled = false
let timer: ReturnType<typeof setTimeout> | undefined = undefined
const finish = (ok: boolean): void => {
if (settled) return
settled = true
Expand All @@ -192,7 +193,7 @@ function verify(binary: string): Promise<boolean> {
finish(false)
return
}
const timer = setTimeout(() => {
timer = setTimeout(() => {
child.kill()
finish(false)
}, PROBE_TIMEOUT_MS)
Expand Down
24 changes: 22 additions & 2 deletions tests/search-engines.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
* machines have no fd (and only sometimes rg), and the probe/runner contracts
* are what the dispatch depends on.
*/
import { afterEach, describe, expect, it } from 'vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import type { ChildProcess } from 'node:child_process'
import { spawn, type ChildProcess } from 'node:child_process'
import { PassThrough } from 'node:stream'
import { join, posix, win32 } from 'node:path'
import { searchFiles } from '../src/fs-search.ts'
Expand All @@ -37,6 +37,26 @@ import { removeTree } from './temp-tree.ts'
const fdProbe: EngineProbe = { engine: 'fd', binary: '/fake/fd' }
const rgProbe: EngineProbe = { engine: 'rg', binary: '/fake/rg' }

vi.mock('node:child_process', { spy: true })

describe('engine probe spawn failures (#995)', () => {
it('falls back to the plain walk when spawning a probe throws synchronously', async () => {
resetEngines()
const dir = mkdtempSync(join(tmpdir(), 'dsh-sidebar-probe-failure-'))
writeFileSync(join(dir, 'needle.txt'), 'found by the plain walk')
vi.mocked(spawn).mockImplementation(() => { throw new Error('spawn blocked') })
try {
await expect(searchFiles(dir, 'needle')).resolves.toMatchObject({ matches: ['needle.txt'] })
expect(spawn).toHaveBeenCalled()
await expect(probeEngines()).resolves.toEqual([])
} finally {
vi.mocked(spawn).mockRestore()
resetEngines()
removeTree(dir)
}
})
})

describe('normalizeEnginePaths', () => {
it('keeps root-relative /-separated lines as-is (fd contract)', () => {
expect(normalizeEnginePaths(['src/util.ts', 'README.md'])).toEqual([
Expand Down