Files
qinglong/test/back/persistedLocalProcessController.test.cjs

146 lines
4.0 KiB
JavaScript

require('ts-node/register/transpile-only');
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createLocalProcessDurableHandle,
} = require('../../back/runtime/adapters/local-process/localProcessIdentity');
const {
LocalProcessPersistedExecutionController,
} = require('../../back/runtime/adapters/local-process/persistedLocalProcessController');
const IDENTITY = {
platform: 'linux',
bootId: '11111111-2222-3333-4444-555555555555',
pid: 4321,
processGroupId: 4321,
startTimeTicks: '123456',
};
const HANDLE = createLocalProcessDurableHandle('handle-1', IDENTITY);
function controller(inspections, overrides = {}) {
const signals = [];
let index = 0;
return {
signals,
value: new LocalProcessPersistedExecutionController({
identityProvider: {
async capture() {
throw new Error('not used');
},
async inspect(identity) {
assert.deepEqual(identity, IDENTITY);
const status = inspections[Math.min(index, inspections.length - 1)];
index += 1;
return { status };
},
},
sendSignal(pid, signal) {
signals.push({ pid, signal });
},
graceMs: 10,
pollIntervalMs: 5,
sleep: async () => undefined,
...overrides,
}),
};
}
const REASON = { kind: 'user', requestedAtMs: 1_750_000_000_000 };
test('refuses malformed, PID-mismatched, and non-leader handles without signals', async () => {
const instance = controller(['running']);
assert.equal(
(await instance.value.stop({ durableHandle: 'bad', reason: REASON }))
.status,
'invalid',
);
assert.equal(
(
await instance.value.stop({
durableHandle: HANDLE,
expectedPid: 9999,
reason: REASON,
})
).status,
'pid_mismatch',
);
const nonLeader = createLocalProcessDurableHandle('handle-2', {
...IDENTITY,
processGroupId: 4000,
});
assert.equal(
(await instance.value.stop({ durableHandle: nonLeader, reason: REASON }))
.status,
'identity_mismatch',
);
assert.deepEqual(instance.signals, []);
});
test('sends TERM only when the persisted process exits during grace', async () => {
const instance = controller(['running', 'exited']);
const result = await instance.value.stop({
durableHandle: HANDLE,
expectedPid: 4321,
reason: REASON,
});
assert.deepEqual(result, {
status: 'termination_requested',
termSignalSent: true,
killSignalSent: false,
});
assert.deepEqual(instance.signals, [{ pid: -4321, signal: 'SIGTERM' }]);
});
test('revalidates identity before escalating a persistent process to KILL', async () => {
const persistent = controller(['running', 'running', 'running', 'running']);
const killed = await persistent.value.stop({
durableHandle: HANDLE,
reason: REASON,
});
assert.equal(killed.killSignalSent, true);
assert.deepEqual(persistent.signals, [
{ pid: -4321, signal: 'SIGTERM' },
{ pid: -4321, signal: 'SIGKILL' },
]);
const changed = controller([
'running',
'running',
'running',
'identity_mismatch',
]);
const refused = await changed.value.stop({
durableHandle: HANDLE,
reason: REASON,
});
assert.equal(refused.status, 'identity_mismatch');
assert.equal(refused.killSignalSent, false);
assert.deepEqual(changed.signals, [{ pid: -4321, signal: 'SIGTERM' }]);
});
test('treats an already exited process and ESRCH as idempotent completion', async () => {
const exited = controller(['exited']);
assert.deepEqual(
await exited.value.stop({ durableHandle: HANDLE, reason: REASON }),
{
status: 'already_exited',
termSignalSent: false,
killSignalSent: false,
},
);
const noProcess = controller(['running'], {
sendSignal() {
const error = new Error('gone');
error.code = 'ESRCH';
throw error;
},
});
assert.equal(
(await noProcess.value.stop({ durableHandle: HANDLE, reason: REASON }))
.status,
'already_exited',
);
});