mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
`lock_root` dereferenced `$HOME` unconditionally, so under `set -u` the script died with "HOME: unbound variable" in any environment that starts without it — even when `PLAYWRIGHT_RESOURCE_LOCK_DIR` made HOME irrelevant. Surfaced by running the suite under vitest's node environment, where `process.env` is patched and spreading it does not carry HOME through to the child. Resolve the lock path in order (explicit override, XDG_CACHE_HOME, HOME) and fail with a clear message only when none of them is set. The test now names PATH and HOME explicitly instead of spreading process.env.
209 lines
7.6 KiB
JavaScript
209 lines
7.6 KiB
JavaScript
import { spawnSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
|
|
const scriptPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'pw-session.sh');
|
|
|
|
// A stand-in for playwright-cli so the lock can be exercised without launching
|
|
// real browser engines. `list` reports the session recorded in liveSession as
|
|
// `status: open`, mirroring the real `playwright-cli list --all` output.
|
|
const FAKE_CLI = `#!/bin/bash
|
|
state="$(dirname "$0")/live-session"
|
|
open_rc="$(dirname "$0")/open-exit-code"
|
|
if [ "\${1:-}" = "list" ]; then
|
|
live="$(cat "$state")"
|
|
[ "$live" = "__UNAVAILABLE__" ] && exit 3
|
|
echo "### Browsers"
|
|
if [ -n "$live" ]; then
|
|
echo "- $live:"
|
|
echo " - status: open"
|
|
fi
|
|
echo "- already-closed:"
|
|
echo " - status: closed"
|
|
exit 0
|
|
fi
|
|
session="\${1#-s=}"
|
|
case "\${2:-}" in
|
|
open)
|
|
rc="$(cat "$open_rc")"
|
|
[ "$rc" = 0 ] && printf '%s' "$session" >"$state"
|
|
exit "$rc"
|
|
;;
|
|
close)
|
|
[ "$(cat "$state")" = "$session" ] && printf '' >"$state"
|
|
exit 0
|
|
;;
|
|
esac
|
|
exit 0
|
|
`;
|
|
|
|
let tempDir;
|
|
|
|
// Status and diagnostics are split across stdout and stderr, so assertions read
|
|
// both streams.
|
|
const run = (...args) => {
|
|
const result = spawnSync(scriptPath, args, {
|
|
encoding: 'utf8',
|
|
// PATH and HOME are named explicitly rather than relying on spreading
|
|
// process.env, which vitest patches per environment.
|
|
env: {
|
|
PATH: process.env.PATH,
|
|
HOME: process.env.HOME,
|
|
PLAYWRIGHT_RESOURCE_LOCK_DIR: path.join(tempDir, 'slot.lock'),
|
|
PLAYWRIGHT_CLI_BIN: path.join(tempDir, 'fake-playwright-cli'),
|
|
PW_SESSION_POLL_SECONDS: '1',
|
|
},
|
|
});
|
|
return { code: result.status, output: `${result.stdout}${result.stderr}` };
|
|
};
|
|
|
|
const lockExists = () => fs.existsSync(path.join(tempDir, 'slot.lock'));
|
|
const setLiveSession = (session) => fs.writeFileSync(path.join(tempDir, 'live-session'), session);
|
|
const liveSession = () => fs.readFileSync(path.join(tempDir, 'live-session'), 'utf8');
|
|
const setOpenExitCode = (code) => fs.writeFileSync(path.join(tempDir, 'open-exit-code'), String(code));
|
|
|
|
beforeEach(() => {
|
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pw-session-test-'));
|
|
const fakeCli = path.join(tempDir, 'fake-playwright-cli');
|
|
fs.writeFileSync(fakeCli, FAKE_CLI, { mode: 0o755 });
|
|
setLiveSession('');
|
|
setOpenExitCode(0);
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('pw-session.sh', () => {
|
|
it('reports an available slot and rejects malformed session names', () => {
|
|
expect(run('status').output).toContain('browser slot is available');
|
|
|
|
const rejected = run('open', 'bad name!');
|
|
expect(rejected.code).toBe(1);
|
|
expect(rejected.output).toContain('session must be 1-40');
|
|
});
|
|
|
|
it('acquires the slot, records the owner, and blocks a second live acquire with exit 75', () => {
|
|
expect(run('open', 'verify-chrome', 'about:blank').output).toContain("acquired browser slot for 'verify-chrome'");
|
|
expect(run('status').output).toContain('Browser: running');
|
|
|
|
const blocked = run('open', 'verify-firefox', 'about:blank');
|
|
expect(blocked.code).toBe(75);
|
|
expect(blocked.output).toContain('do not bypass the lock');
|
|
});
|
|
|
|
it('refuses to release or steal a slot owned by another session', () => {
|
|
run('open', 'verify-chrome', 'about:blank');
|
|
|
|
const released = run('release', 'verify-firefox');
|
|
expect(released.code).toBe(1);
|
|
expect(released.output).toContain("cannot release the slot held by 'verify-chrome'");
|
|
|
|
// Closing a different session must still stop that browser without
|
|
// dropping someone else's lock.
|
|
expect(run('close', 'verify-firefox').output).toContain("left the slot held by 'verify-chrome' untouched");
|
|
expect(lockExists()).toBe(true);
|
|
});
|
|
|
|
it('releases the slot when the owner closes it', () => {
|
|
run('open', 'verify-chrome', 'about:blank');
|
|
|
|
expect(run('close', 'verify-chrome').output).toContain("released browser slot for 'verify-chrome'");
|
|
expect(lockExists()).toBe(false);
|
|
});
|
|
|
|
it('reclaims a stale slot whose browser is gone instead of blocking forever', () => {
|
|
run('open', 'verify-chrome', 'about:blank');
|
|
setLiveSession(''); // the browser died without releasing the lock
|
|
|
|
const status = run('status');
|
|
expect(status.output).toContain('stale lock');
|
|
expect(status.output).toContain('reclaims this slot automatically');
|
|
|
|
const reclaimed = run('open', 'verify-firefox', 'about:blank');
|
|
expect(reclaimed.code).toBe(0);
|
|
expect(reclaimed.output).toContain("reclaimed stale slot from 'verify-chrome'");
|
|
});
|
|
|
|
it('still stops the browser when the lock was already lost', () => {
|
|
run('open', 'verify-chrome', 'about:blank');
|
|
fs.rmSync(path.join(tempDir, 'slot.lock'), { recursive: true });
|
|
|
|
expect(run('close', 'verify-chrome').output).toContain("already free; closed 'verify-chrome' anyway");
|
|
expect(liveSession()).toBe('');
|
|
});
|
|
|
|
it('releases the slot when the browser fails to start', () => {
|
|
setOpenExitCode(1);
|
|
|
|
const failed = run('open', 'verify-chrome', 'about:blank');
|
|
expect(failed.code).toBe(1);
|
|
expect(failed.output).toContain('released browser slot');
|
|
expect(lockExists()).toBe(false);
|
|
});
|
|
|
|
it('leaves the lock alone when browser liveness cannot be verified', () => {
|
|
run('open', 'verify-chrome', 'about:blank');
|
|
setLiveSession('__UNAVAILABLE__'); // playwright-cli list fails
|
|
|
|
expect(run('status').output).toContain('unverifiable');
|
|
|
|
// Failing closed matters: a broken CLI must not silently disable the budget.
|
|
const blocked = run('open', 'verify-firefox', 'about:blank');
|
|
expect(blocked.code).toBe(75);
|
|
expect(lockExists()).toBe(true);
|
|
});
|
|
|
|
it('polls for a busy slot with --wait and gives up with exit 75', () => {
|
|
run('open', 'verify-chrome', 'about:blank');
|
|
|
|
const timedOut = run('open', '--wait=2', 'verify-firefox', 'about:blank');
|
|
expect(timedOut.code).toBe(75);
|
|
expect(timedOut.output).toContain('gave up after 2s');
|
|
expect(timedOut.output).toContain('retrying in 1s');
|
|
|
|
const malformed = run('open', '--wait=soon', 'verify-firefox');
|
|
expect(malformed.code).toBe(1);
|
|
expect(malformed.output).toContain('whole number of seconds');
|
|
});
|
|
|
|
it('honours --wait after the session name instead of passing it to playwright-cli', () => {
|
|
run('open', 'verify-chrome', 'about:blank');
|
|
|
|
const afterSession = run('open', 'verify-firefox', '--wait=2', 'about:blank');
|
|
expect(afterSession.code).toBe(75);
|
|
expect(afterSession.output).toContain('gave up after 2s');
|
|
});
|
|
|
|
it('fails loudly instead of spinning when a stale lock cannot be removed', () => {
|
|
run('open', 'verify-chrome', 'about:blank');
|
|
setLiveSession(''); // stale, but the lock directory is not removable
|
|
fs.chmodSync(tempDir, 0o500);
|
|
|
|
try {
|
|
const stuck = run('open', 'verify-firefox', 'about:blank');
|
|
expect(stuck.code).toBe(75);
|
|
expect(stuck.output).toContain('could not reclaim the stale slot');
|
|
} finally {
|
|
fs.chmodSync(tempDir, 0o700);
|
|
}
|
|
});
|
|
|
|
it('opens with no extra playwright-cli arguments', () => {
|
|
// Bash 3.2 errors on an empty array expansion under `set -u`, so the
|
|
// no-arguments path needs its own guard.
|
|
const opened = run('open', 'verify-chrome');
|
|
expect(opened.code).toBe(0);
|
|
expect(opened.output).toContain("acquired browser slot for 'verify-chrome'");
|
|
});
|
|
|
|
it('prints usage for an unknown subcommand', () => {
|
|
const unknown = run('bogus');
|
|
expect(unknown.code).toBe(1);
|
|
expect(unknown.output).toContain('Usage:');
|
|
});
|
|
});
|