/** * Tests for electron/update-marker.cjs — the in-app update mutual-exclusion * marker that prevents a desktop relaunched mid-update from spawning a backend * the updater then kills in a loop (#50238). * * Run with: node --test electron/update-marker.test.cjs * (Wired into npm test:desktop:platforms in package.json.) * * Why this matters: the gate must (a) report a live update only when the * updater pid is alive AND the marker is fresh, (b) treat absent/malformed/ * dead-pid/expired markers as "no live update" so a crashed updater can't * strand future launches, and (c) self-heal by deleting a stale marker file. */ const test = require('node:test') const assert = require('node:assert/strict') const fs = require('fs') const os = require('os') const path = require('path') const { markerPath, isPidAlive, readLiveUpdateMarker, UPDATE_MARKER_MAX_AGE_MS } = require('./update-marker.cjs') function tmpHome(tag) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), `hermes-marker-${tag}-`)) return dir } function writeMarker(home, pid, startedAtSec) { fs.writeFileSync(markerPath(home), `${pid}\n${startedAtSec}`) } const ALIVE = () => true // injected kill that "succeeds" => pid alive const DEAD = () => { const err = new Error('no such process') err.code = 'ESRCH' throw err } test('absent marker => no live update', () => { const home = tmpHome('absent') assert.equal(readLiveUpdateMarker(home, { kill: ALIVE }), null) }) test('live pid within age ceiling => live update reported', () => { const home = tmpHome('live') const now = 1_000_000_000_000 writeMarker(home, 4242, Math.floor(now / 1000) - 5) // 5s old const res = readLiveUpdateMarker(home, { kill: ALIVE, now: () => now }) assert.ok(res, 'a fresh, alive marker is a live update') assert.equal(res.pid, 4242) assert.ok(res.ageMs >= 0 && res.ageMs < 10_000) assert.ok(fs.existsSync(markerPath(home)), 'a live marker is NOT deleted') }) test('dead pid => no live update and marker is pruned', () => { const home = tmpHome('dead') writeMarker(home, 999999, Math.floor(Date.now() / 1000)) assert.equal(readLiveUpdateMarker(home, { kill: DEAD }), null) assert.ok(!fs.existsSync(markerPath(home)), 'a dead-pid marker self-heals (deleted)') }) test('expired marker (past age ceiling) => no live update and pruned', () => { const home = tmpHome('expired') const now = 1_000_000_000_000 writeMarker(home, 4242, Math.floor((now - UPDATE_MARKER_MAX_AGE_MS - 60_000) / 1000)) // Even though the pid is "alive", the marker is too old to trust. assert.equal(readLiveUpdateMarker(home, { kill: ALIVE, now: () => now }), null) assert.ok(!fs.existsSync(markerPath(home)), 'an expired marker self-heals (deleted)') }) test('malformed marker => no live update and pruned', () => { const home = tmpHome('malformed') fs.writeFileSync(markerPath(home), 'not-a-pid\nnonsense') assert.equal(readLiveUpdateMarker(home, { kill: ALIVE }), null) assert.ok(!fs.existsSync(markerPath(home))) }) test('isPidAlive: own pid is alive, impossible pid is dead', () => { assert.equal(isPidAlive(process.pid), true) assert.equal(isPidAlive(-1), false) assert.equal(isPidAlive(0), false) assert.equal(isPidAlive(NaN), false) }) test('isPidAlive: EPERM counts as alive (process owned by another user)', () => { const eperm = () => { const err = new Error('operation not permitted') err.code = 'EPERM' throw err } assert.equal(isPidAlive(4242, eperm), true) })