fix(desktop): make the zsh probe test legs visible and run them on CI
The zsh login-shell legs in remote-lifecycle.test.ts and ssh-connection.test.ts silently returned when zsh was missing, and the js-tests runner image ships no zsh, so the #111949 coverage never ran on CI and a wrapper regression stayed green. - js-tests.yml: install zsh on the Linux runner before the checks. - Both legs now report vitest skips ('zsh not installed') instead of passing; the ssh-connection leg is its own test so the skip is visible. - Docs: note the zsh degraded mode (no process-group kill for a hung probe's grandchildren) in the SSH connection guide.
This commit is contained in:
@@ -23,6 +23,13 @@ jobs:
|
||||
node-version: 26
|
||||
cache: npm
|
||||
|
||||
# The desktop SSH watchdog tests run their probe through a real zsh
|
||||
# login shell (#111949). The ubuntu image ships no zsh and the tests
|
||||
# skip without it, so install it here or those legs never run on CI.
|
||||
- name: Install zsh
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get install -y zsh
|
||||
|
||||
- name: grab npm 12
|
||||
run: |
|
||||
# No-op once the bundled npm is already 12.x — saves ~5-15s/job and
|
||||
|
||||
@@ -1813,17 +1813,17 @@ test('remote SSH ownership capability requires both secure bootstrap flags', asy
|
||||
assert.equal(await remoteSupportsSshOwnership(unsupported, '/x/hermes'), false)
|
||||
})
|
||||
|
||||
test('capability probe survives a zsh login shell on the remote (#111949)', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
return
|
||||
}
|
||||
|
||||
test.skipIf(process.platform === 'win32')('capability probe survives a zsh login shell on the remote (#111949)', async t => {
|
||||
// sshd runs the remote command under the account's LOGIN shell. A bare
|
||||
// `set -m` is fatal in a non-interactive zsh, so the watchdog-wrapped probe
|
||||
// used to return nothing and a current remote was reported as unsupported.
|
||||
const zsh = await exec('command -v zsh || true').then(r => r.stdout.trim())
|
||||
|
||||
// CI installs zsh (js-tests.yml); locally a missing zsh must show as a
|
||||
// skip, not a pass, or a wrapper regression stays green unnoticed.
|
||||
if (!zsh) {
|
||||
t.skip('zsh not installed')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1074,6 +1074,25 @@ test('stopTunnelChild waits for process exit', async () => {
|
||||
assert.equal(stopped, true)
|
||||
})
|
||||
|
||||
test.skipIf(process.platform === 'win32')('withRemoteTimeout runs a healthy probe under a zsh login shell (#111949)', async t => {
|
||||
// SSH runs the remote command through the account's login shell. In
|
||||
// non-interactive zsh, a bare `set -m` is fatal, so the wrapper must still
|
||||
// run a healthy probe rather than reporting the remote as unsupported.
|
||||
const zsh = await execFileAsync('sh', ['-c', 'command -v zsh || true']).then(r => r.stdout.trim())
|
||||
|
||||
// CI installs zsh (js-tests.yml); locally a missing zsh must show as a
|
||||
// skip, not a pass, or a wrapper regression stays green unnoticed.
|
||||
if (!zsh) {
|
||||
t.skip('zsh not installed')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const { stdout: zshStdout } = await execFileAsync(zsh, ['-fc', withRemoteTimeout('echo zsh-ok', 5)])
|
||||
|
||||
assert.equal(zshStdout, 'zsh-ok\n')
|
||||
})
|
||||
|
||||
test('withRemoteTimeout kills a hung probe remotely instead of orphaning it (#110478)', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
return
|
||||
@@ -1092,17 +1111,6 @@ test('withRemoteTimeout kills a hung probe remotely instead of orphaning it (#11
|
||||
)
|
||||
assert.ok(REMOTE_PROBE_TIMEOUT_SECS * 1000 < 20_000, 'remote watchdog fires before the local exec timeout')
|
||||
|
||||
// SSH runs the remote command through the account's login shell. In
|
||||
// non-interactive zsh, a bare `set -m` is fatal, so the wrapper must still
|
||||
// run a healthy probe rather than reporting the remote as unsupported.
|
||||
const zsh = await execFileAsync('sh', ['-c', 'command -v zsh || true']).then(r => r.stdout.trim())
|
||||
|
||||
if (zsh) {
|
||||
const { stdout: zshStdout } = await execFileAsync(zsh, ['-fc', withRemoteTimeout('echo zsh-ok', 5)])
|
||||
|
||||
assert.equal(zshStdout, 'zsh-ok\n')
|
||||
}
|
||||
|
||||
// Behavior through a real POSIX shell: healthy output passes through …
|
||||
const healthyStart = Date.now()
|
||||
const { stdout } = await execFileAsync('sh', ['-c', withRemoteTimeout('echo hello', 5)])
|
||||
|
||||
@@ -130,7 +130,10 @@ authentication; manage sign-in from the registered connection controls.
|
||||
- *SSH only:*
|
||||
- **SSH host** — one composite field in `user@host:22` form (user and
|
||||
port optional). Your SSH key is used; the app adopts a dashboard
|
||||
token over the tunnel.
|
||||
token over the tunnel. Remote probes run under the account's login
|
||||
shell; on a `zsh` login shell the probe watchdog cannot kill the whole
|
||||
process group, so a hung probe's grandchildren may linger on the remote
|
||||
(bash/sh remotes reap them).
|
||||
5. Click **Save connection** (or **Cancel**).
|
||||
6. Click **Test** on the new row and wait for *"Reachable"*.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user