fix: time out system font listing (#3786)
This commit is contained in:
parent
ac8146ab67
commit
25c83796d8
|
|
@ -0,0 +1,47 @@
|
|||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { execFileMock, killMock } = vi.hoisted(() => ({
|
||||
execFileMock: vi.fn(),
|
||||
killMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('child_process', () => ({
|
||||
execFile: execFileMock
|
||||
}))
|
||||
|
||||
function expectedFallbackFont(): string {
|
||||
if (process.platform === 'darwin') {
|
||||
return 'SF Mono'
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
return 'Cascadia Mono'
|
||||
}
|
||||
return 'JetBrains Mono'
|
||||
}
|
||||
|
||||
describe('listSystemFontFamilies', () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.resetModules()
|
||||
execFileMock.mockReset()
|
||||
killMock.mockReset()
|
||||
})
|
||||
|
||||
it('falls back when the platform font command never exits', async () => {
|
||||
vi.useFakeTimers()
|
||||
execFileMock.mockReturnValue({ kill: killMock })
|
||||
|
||||
const { listSystemFontFamilies } = await import('./system-fonts')
|
||||
const fontsPromise = listSystemFontFamilies()
|
||||
let resolvedFonts: string[] | null = null
|
||||
fontsPromise.then((fonts) => {
|
||||
resolvedFonts = fonts
|
||||
})
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000)
|
||||
|
||||
expect(resolvedFonts).not.toBeNull()
|
||||
expect(resolvedFonts).toContain(expectedFallbackFont())
|
||||
expect(killMock).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
|
|
@ -2,6 +2,7 @@ import { execFile } from 'child_process'
|
|||
|
||||
let cachedFonts: string[] | null = null
|
||||
let fontsPromise: Promise<string[]> | null = null
|
||||
const SYSTEM_FONT_LIST_TIMEOUT_MS = 15_000
|
||||
|
||||
export async function listSystemFontFamilies(): Promise<string[]> {
|
||||
if (cachedFonts) {
|
||||
|
|
@ -96,13 +97,37 @@ $fonts.Families | ForEach-Object { $_.Name }
|
|||
|
||||
function execFileText(command: string, args: string[], maxBuffer: number): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(command, args, { encoding: 'utf8', maxBuffer }, (error, stdout) => {
|
||||
let settled = false
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
const child = execFile(command, args, { encoding: 'utf8', maxBuffer }, (error, stdout) => {
|
||||
if (settled) {
|
||||
return
|
||||
}
|
||||
settled = true
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(stdout)
|
||||
})
|
||||
if (!settled) {
|
||||
timer = setTimeout(() => {
|
||||
if (settled) {
|
||||
return
|
||||
}
|
||||
settled = true
|
||||
// Why: font discovery is a startup convenience; a stuck OS font tool
|
||||
// should fall back instead of keeping settings IPC pending forever.
|
||||
child.kill()
|
||||
reject(new Error(`Timed out listing system fonts with ${command}`))
|
||||
}, SYSTEM_FONT_LIST_TIMEOUT_MS)
|
||||
if (typeof timer === 'object' && 'unref' in timer) {
|
||||
timer.unref()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue