mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
fix: verify WSL1 execution when WSL2 kernel is missing
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { execFile, execFileSync } from 'node:child_process'
|
||||
import { resolveWslInteropSpawnCwd } from './wsl-interop-spawn-directory'
|
||||
import {
|
||||
canExecuteWslWithoutKernel,
|
||||
canExecuteWslWithoutKernelSync,
|
||||
isWslMissingKernelError
|
||||
} from './wsl-missing-kernel-probe'
|
||||
|
||||
type WslAvailabilityCache =
|
||||
| { available: true }
|
||||
@@ -147,6 +152,9 @@ export function isWslAvailable(): boolean {
|
||||
})
|
||||
return cacheWslAvailabilityProbeResult(null, startedAtGeneration)
|
||||
} catch (error) {
|
||||
if (isWslMissingKernelError(error) && canExecuteWslWithoutKernelSync()) {
|
||||
return cacheWslAvailabilityProbeResult(null, startedAtGeneration)
|
||||
}
|
||||
return cacheWslAvailabilityProbeResult(error, startedAtGeneration)
|
||||
}
|
||||
}
|
||||
@@ -176,7 +184,12 @@ export function isWslAvailableAsync(): Promise<boolean> {
|
||||
const startedAtGeneration = wslAvailabilityCacheGeneration
|
||||
wslAvailabilityProbeInFlight = probeWslStatus()
|
||||
.then(() => cacheWslAvailabilityProbeResult(null, startedAtGeneration))
|
||||
.catch((error: unknown) => cacheWslAvailabilityProbeResult(error, startedAtGeneration))
|
||||
.catch(async (error: unknown) => {
|
||||
if (isWslMissingKernelError(error) && (await canExecuteWslWithoutKernel())) {
|
||||
return cacheWslAvailabilityProbeResult(null, startedAtGeneration)
|
||||
}
|
||||
return cacheWslAvailabilityProbeResult(error, startedAtGeneration)
|
||||
})
|
||||
.finally(() => {
|
||||
wslAvailabilityProbeInFlight = null
|
||||
})
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { execFile, execFileSync } from 'node:child_process'
|
||||
import { runProcess, runProcessSync } from '../shared/child-process/run-process'
|
||||
import {
|
||||
_resetWslAvailabilityCacheForTests,
|
||||
isWslAvailable,
|
||||
isWslAvailableAsync
|
||||
} from './wsl-availability'
|
||||
|
||||
vi.mock('node:child_process', () => ({ execFile: vi.fn(), execFileSync: vi.fn() }))
|
||||
vi.mock('../shared/child-process/run-process', () => ({
|
||||
runProcess: vi.fn(),
|
||||
runProcessSync: vi.fn()
|
||||
}))
|
||||
vi.mock('./wsl-interop-spawn-directory', () => ({
|
||||
resolveWslInteropSpawnCwd: () => 'C:\\Windows'
|
||||
}))
|
||||
|
||||
const originalPlatform = process.platform
|
||||
const success = { code: 0, signal: null, stdout: '', stderr: '', timedOut: false }
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
Object.defineProperty(process, 'platform', { value: 'win32' })
|
||||
_resetWslAvailabilityCacheForTests()
|
||||
})
|
||||
afterEach(() => {
|
||||
Object.defineProperty(process, 'platform', { value: originalPlatform })
|
||||
_resetWslAvailabilityCacheForTests()
|
||||
})
|
||||
|
||||
for (const mode of ['sync', 'async'] as const) {
|
||||
describe(`${mode} WSL1 availability without WSL2 kernel`, () => {
|
||||
const probe = () => (mode === 'sync' ? isWslAvailable() : isWslAvailableAsync())
|
||||
function failStatus(code: number): void {
|
||||
vi.mocked(execFileSync).mockImplementation(() => {
|
||||
throw { status: code }
|
||||
})
|
||||
vi.mocked(execFile).mockImplementation((...args: unknown[]) => {
|
||||
const callback = args.at(-1) as (error: unknown) => void
|
||||
callback({ code })
|
||||
return {} as ReturnType<typeof execFile>
|
||||
})
|
||||
}
|
||||
function guestResult(result: typeof success): void {
|
||||
vi.mocked(runProcess).mockResolvedValue(result)
|
||||
vi.mocked(runProcessSync).mockReturnValue(result)
|
||||
}
|
||||
|
||||
it('requires guest execution and caches its success', async () => {
|
||||
failStatus(-444)
|
||||
guestResult(success)
|
||||
expect(await probe()).toBe(true)
|
||||
expect(await probe()).toBe(true)
|
||||
const runner = mode === 'sync' ? runProcessSync : runProcess
|
||||
expect(runner).toHaveBeenCalledTimes(1)
|
||||
expect(runner).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
program: 'wsl.exe',
|
||||
args: ['--exec', '/bin/true'],
|
||||
timeoutMs: 5000,
|
||||
cwd: 'C:\\Windows'
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
for (const result of [
|
||||
{ ...success, code: 1 },
|
||||
{ ...success, timedOut: true }
|
||||
]) {
|
||||
it(`keeps failed guest unavailable: ${JSON.stringify(result)}`, async () => {
|
||||
failStatus(-444)
|
||||
guestResult(result)
|
||||
expect(await probe()).toBe(false)
|
||||
})
|
||||
}
|
||||
|
||||
it('does not probe a guest for unrelated status failures', async () => {
|
||||
failStatus(1)
|
||||
expect(await probe()).toBe(false)
|
||||
expect(runProcess).not.toHaveBeenCalled()
|
||||
expect(runProcessSync).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { runProcess, runProcessSync, type ProcessSpec } from '../shared/child-process/run-process'
|
||||
import { buildWslExecArgs } from '../shared/wsl-login-shell-command'
|
||||
import { resolveWslInteropSpawnCwd } from './wsl-interop-spawn-directory'
|
||||
|
||||
export function isWslMissingKernelError(error: unknown): boolean {
|
||||
const failure = error as { code?: unknown; status?: unknown } | null
|
||||
return failure?.code === -444 || failure?.status === -444
|
||||
}
|
||||
|
||||
function defaultGuestProbe(): ProcessSpec {
|
||||
return {
|
||||
program: 'wsl.exe',
|
||||
args: buildWslExecArgs(undefined, ['/bin/true']),
|
||||
cwd: resolveWslInteropSpawnCwd(),
|
||||
timeoutMs: 5000,
|
||||
maxOutputBytes: 4096
|
||||
}
|
||||
}
|
||||
|
||||
// WSL1 can execute normally while --status rejects a missing WSL2 kernel.
|
||||
export async function canExecuteWslWithoutKernel(): Promise<boolean> {
|
||||
try {
|
||||
const result = await runProcess(defaultGuestProbe())
|
||||
return result.code === 0 && !result.timedOut
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function canExecuteWslWithoutKernelSync(): boolean {
|
||||
try {
|
||||
const result = runProcessSync(defaultGuestProbe())
|
||||
return result.code === 0 && !result.timedOut
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user