Files
orca/src/main/wsl-availability-missing-kernel.test.ts
T
OrcaWinandOrca Worker fb5d016887 fix(wsl): start every host wsl.exe call through process-host so no console window flashes (#27628)
* fix(wsl): start host-side wsl.exe probes through process-host so they never open a console

wsl --list, wsl --status, the WSL home and directory probes, and WSL UNC
deletes were started with node:child_process directly. The synchronous ones
omitted windowsHide, so each spawn from the GUI-subsystem main process
flashed a conhost window that could take keyboard focus. Route them through
@orca/process-host, which pins windowsHide, and keep the execFile-shaped exit
errors the availability cache classifies.

Refs #27390

* build(tc): list wsl-exe-process.ts in the web typecheck project

wsl-availability.ts is in the explicit web file list and now imports it.

* refactor(wsl): drop the unused sync availability probe; classify ProcessResult directly

No production code calls the sync isWslAvailable (the IPC and RPC paths
already assert they never do), so delete it and its sync guest-probe twin
instead of porting them. The async probe now reads the --status
ProcessResult itself: an exit code is definitive, a timeout or signal kill
is retryable, ENOENT on spawn is definitive. The WSL2-missing-kernel exit
codes live in one named set. Availability tests now drive the async probe.

* refactor(wsl): shrink wsl-exe-process to a spec builder and stdout reader

With the availability cache classifying ProcessResult itself, nothing reads
the execFile-shaped code/status error any more. wslExeFailure returns a
plain reason string; wslExeStdout throws it; the UNC delete folds it into
its own error. The home probe builds its argv with buildWslExecArgs.

* test(wsl): mock process-host in the empty running-list case from main

* docs(wsl): the availability probe no longer blocks; drop the stale word

* build(ratchet): prune the wsl.exe sync launches this branch removed

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 14:51:32 -07:00

89 lines
2.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { runProcess } from '@orca/process-host'
import type { ProcessResult, ProcessSpec } from '@orca/process-host/process-spec'
import { _resetWslAvailabilityCacheForTests, isWslAvailableAsync } from './wsl-availability'
vi.mock('@orca/process-host', () => ({
runProcess: vi.fn()
}))
vi.mock('./wsl-interop-spawn-directory', () => ({
resolveWslInteropSpawnCwd: () => 'C:\\Windows'
}))
const originalPlatform = process.platform
const success: ProcessResult = { code: 0, signal: null, stdout: '', stderr: '', timedOut: false }
describe('WSL1 availability without WSL2 kernel', () => {
const guestCalls = () =>
vi.mocked(runProcess).mock.calls.filter(([spec]) => spec.args?.[0] === '--exec')
let statusCode = 0
let guest: () => ProcessResult = () => success
function failStatus(code: number): void {
statusCode = code
}
function guestResult(result: ProcessResult): void {
guest = () => result
}
function respond(spec: ProcessSpec): ProcessResult {
return spec.args?.[0] === '--status' ? { ...success, code: statusCode } : guest()
}
beforeEach(() => {
vi.resetAllMocks()
Object.defineProperty(process, 'platform', { value: 'win32' })
_resetWslAvailabilityCacheForTests()
statusCode = 0
guest = () => success
vi.mocked(runProcess).mockImplementation(async (spec) => respond(spec))
})
afterEach(() => {
Object.defineProperty(process, 'platform', { value: originalPlatform })
_resetWslAvailabilityCacheForTests()
})
// Node reports the Windows DWORD; the console prints its signed equivalent.
for (const status of [-444, 4_294_966_852]) {
it(`requires guest execution and caches its success for ${status}`, async () => {
failStatus(status)
guestResult(success)
expect(await isWslAvailableAsync()).toBe(true)
expect(await isWslAvailableAsync()).toBe(true)
expect(guestCalls()).toHaveLength(1)
expect(runProcess).toHaveBeenCalledWith(
expect.objectContaining({
program: 'wsl.exe',
args: ['--exec', '/bin/true'],
timeoutMs: 5000,
cwd: 'C:\\Windows'
})
)
})
}
for (const result of [
{ ...success, code: 1 },
{ ...success, code: null, timedOut: true }
]) {
it(`keeps a failed guest unavailable: ${JSON.stringify(result)}`, async () => {
failStatus(-444)
guestResult(result)
expect(await isWslAvailableAsync()).toBe(false)
})
}
it('stays unavailable when the guest probe cannot be spawned', async () => {
failStatus(-444)
guest = () => {
throw new Error('EPERM')
}
expect(await isWslAvailableAsync()).toBe(false)
})
it('does not probe a guest for unrelated status failures', async () => {
failStatus(1)
expect(await isWslAvailableAsync()).toBe(false)
expect(guestCalls()).toHaveLength(0)
})
})