Files
hermes-agent/apps/desktop/electron/singleton-lock.test.ts
Brooklyn Nicholson 3cee485bc2 style(desktop): satisfy lint on the singleton-lock additions
Import-order and curly rules the new electron files missed.
2026-09-29 18:43:14 -05:00

90 lines
3.4 KiB
TypeScript

import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { test } from 'vitest'
import {
isStaleSingletonLockOwner,
parseProcStateField,
parseSingletonLockPid,
removeStaleSingletonLock
} from './singleton-lock'
test('parseSingletonLockPid reads the owning PID only for this hostname', () => {
assert.equal(parseSingletonLockPid('myhost-4711', 'myhost'), 4711)
// A shared home directory can carry another machine's lock; never judge it.
assert.equal(parseSingletonLockPid('otherhost-4711', 'myhost'), null)
assert.equal(parseSingletonLockPid('myhost-4711-extra', 'myhost'), null)
assert.equal(parseSingletonLockPid('myhost-0', 'myhost'), null)
assert.equal(parseSingletonLockPid('myhost-notapid', 'myhost'), null)
assert.equal(parseSingletonLockPid('', 'myhost'), null)
assert.equal(parseSingletonLockPid(undefined, 'myhost'), null)
})
test('parseProcStateField survives a comm containing spaces and parentheses', () => {
const stat = '4711 (Hermes Desktop (zygote)) Z 1 1 0 0 -1 4194560'
assert.equal(parseProcStateField(stat), 'Z')
assert.equal(parseProcStateField('4711 (node) S 1 1 0 0 -1'), 'S')
assert.equal(parseProcStateField('no parentheses at all'), null)
})
test('a zombie or missing owner is stale; kill(pid,0) alone would say alive', () => {
// #78101: a defunct process still answers kill(pid, 0) until reaped, so the
// state field — not a signal probe — is the boundary.
assert.equal(isStaleSingletonLockOwner('Z'), true)
assert.equal(isStaleSingletonLockOwner(null), true)
assert.equal(isStaleSingletonLockOwner(undefined), true)
assert.equal(isStaleSingletonLockOwner('S'), false)
assert.equal(isStaleSingletonLockOwner('R'), false)
assert.equal(isStaleSingletonLockOwner(''), false)
})
test('removeStaleSingletonLock unlinks only a provably-dead owner', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-singleton-lock-'))
try {
const lockPath = path.join(root, 'SingletonLock')
// Live owner (state S): lock stays, nothing reported.
fs.symlinkSync('myhost-4711', lockPath, 'file')
assert.equal(
removeStaleSingletonLock(root, { platform: 'linux', hostname: 'myhost', readProcState: () => 'S' }),
null
)
assert.equal(fs.readlinkSync(lockPath), 'myhost-4711')
// Zombie owner (#78101): lock removed, PID reported.
assert.equal(
removeStaleSingletonLock(root, { platform: 'linux', hostname: 'myhost', readProcState: () => 'Z' }),
4711
)
assert.equal(fs.existsSync(lockPath), false)
// Dead owner (no /proc entry): lock removed too.
fs.symlinkSync('myhost-4711', lockPath, 'file')
assert.equal(
removeStaleSingletonLock(root, { platform: 'linux', hostname: 'myhost', readProcState: () => null }),
4711
)
// Another machine's lock on a shared home: never touched.
fs.symlinkSync('otherhost-4711', lockPath, 'file')
assert.equal(
removeStaleSingletonLock(root, { platform: 'linux', hostname: 'myhost', readProcState: () => 'Z' }),
null
)
assert.equal(fs.readlinkSync(lockPath), 'otherhost-4711')
// No lock at all: nothing happens.
fs.unlinkSync(lockPath)
assert.equal(
removeStaleSingletonLock(root, { platform: 'linux', hostname: 'myhost', readProcState: () => null }),
null
)
} finally {
fs.rmSync(root, { recursive: true, force: true })
}
})