mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-15 19:57:07 +08:00
* fix: harden task lifecycle and scheduler readiness * fix: confine log writes to the configured log directory * fix: verify complete build artifacts and untracked inputs * fix: reconcile scheduler state and make stop win startup races * fix: isolate cron generations and serialize scheduler recovery
179 lines
5.6 KiB
JavaScript
179 lines
5.6 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { spawn, spawnSync } = require('node:child_process');
|
|
const helper = path.resolve('shell/node_path_cache.sh');
|
|
const hasFlock = spawnSync('/bin/bash', ['-c', 'type -P flock']).status === 0;
|
|
function fixture(t) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-path-lock-'));
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const bin = path.join(root, 'bin');
|
|
fs.mkdirSync(bin);
|
|
fs.symlinkSync(process.execPath, path.join(bin, 'node'));
|
|
fs.writeFileSync(
|
|
path.join(bin, 'pnpm'),
|
|
'#!/bin/bash\nprintf "call\\n" >> "$CALLS"\n[[ -n "${UMASK_FILE:-}" ]] && umask > "$UMASK_FILE"\nsleep "${LOOKUP_DELAY:-0.2}"\n[[ "${FAIL:-0}" == 1 ]] && exit 17\nprintf "%s\\n" "${npm_config_global_dir:-/test/global/node_modules}"\n',
|
|
{ mode: 0o755 },
|
|
);
|
|
const env = {
|
|
...process.env,
|
|
HOME: root,
|
|
PATH: bin + ':' + process.env.PATH,
|
|
dir_tmp: path.join(root, 'cache'),
|
|
CALLS: path.join(root, 'calls'),
|
|
HELPER: helper,
|
|
};
|
|
const command = '. "$HELPER"; ql_get_node_global_path';
|
|
const run = (extra = {}, code = command) =>
|
|
spawnSync('/bin/bash', ['-euc', code], {
|
|
cwd: root,
|
|
env: { ...env, ...extra },
|
|
encoding: 'utf8',
|
|
});
|
|
const asyncRun = (extra = {}) =>
|
|
new Promise((resolve, reject) => {
|
|
const child = spawn('/bin/bash', ['-euc', command], {
|
|
cwd: root,
|
|
env: { ...env, ...extra },
|
|
});
|
|
let out = '',
|
|
err = '';
|
|
child.stdout.on('data', (c) => (out += c));
|
|
child.stderr.on('data', (c) => (err += c));
|
|
child.on('error', reject);
|
|
child.on('close', (status) =>
|
|
resolve({ status, stdout: out, stderr: err }),
|
|
);
|
|
});
|
|
const count = () =>
|
|
fs.existsSync(env.CALLS)
|
|
? fs.readFileSync(env.CALLS, 'utf8').trim().split('\n').length
|
|
: 0;
|
|
const cache = path.join(env.dir_tmp, `pnpm-root-${process.getuid()}.cache`);
|
|
return {
|
|
root,
|
|
bin,
|
|
env,
|
|
command,
|
|
run,
|
|
asyncRun,
|
|
count,
|
|
cache,
|
|
lock: cache + '.lock',
|
|
};
|
|
}
|
|
function check(r, output = '/test/global/node_modules') {
|
|
assert.equal(r.status, 0, r.stderr);
|
|
assert.equal(r.stdout.trim(), output);
|
|
}
|
|
test(
|
|
'simultaneous cold and expired lookups share one successful refresh',
|
|
{ skip: !hasFlock },
|
|
async (t) => {
|
|
const f = fixture(t);
|
|
for (let batch = 0; batch < 2; batch++) {
|
|
if (batch) {
|
|
const lines = fs.readFileSync(f.cache, 'utf8').split('\n');
|
|
lines[1] = '0';
|
|
fs.writeFileSync(f.cache, lines.join('\n'));
|
|
}
|
|
const rows = await Promise.all(
|
|
Array.from({ length: 8 }, () => f.asyncRun({ LOOKUP_DELAY: '0.5' })),
|
|
);
|
|
rows.forEach((r) => check(r));
|
|
assert.equal(f.count(), batch + 1);
|
|
}
|
|
assert.equal(fs.statSync(f.lock).mode & 0o777, 0o600);
|
|
check(f.run());
|
|
assert.equal(f.count(), 2);
|
|
},
|
|
);
|
|
test(
|
|
'waiters with different configuration do not reuse another key',
|
|
{ skip: !hasFlock },
|
|
async (t) => {
|
|
const f = fixture(t);
|
|
const rows = await Promise.all(
|
|
['/first', '/second'].map((v) =>
|
|
f.asyncRun({ npm_config_global_dir: v }),
|
|
),
|
|
);
|
|
rows.forEach((r, i) => check(r, ['/first', '/second'][i]));
|
|
assert.equal(f.count(), 2);
|
|
},
|
|
);
|
|
test(
|
|
'lock timeout falls back while a holder is still alive',
|
|
{ skip: !hasFlock },
|
|
async (t) => {
|
|
const f = fixture(t);
|
|
fs.mkdirSync(f.env.dir_tmp);
|
|
const holder = spawn(
|
|
'/bin/bash',
|
|
['-c', 'exec 9>> "$LOCK"; flock 9; echo ready; sleep 30'],
|
|
{ env: { ...f.env, LOCK: f.lock }, detached: true },
|
|
);
|
|
t.after(() => {
|
|
try {
|
|
process.kill(-holder.pid, 'SIGKILL');
|
|
} catch {}
|
|
});
|
|
await new Promise((resolve, reject) => {
|
|
holder.stdout.once('data', resolve);
|
|
holder.once('error', reject);
|
|
});
|
|
const start = Date.now();
|
|
check(f.run());
|
|
assert.ok(Date.now() - start >= 1800);
|
|
assert.equal(holder.exitCode, null);
|
|
assert.equal(f.count(), 1);
|
|
},
|
|
);
|
|
test('absent or unsupported flock and unsafe lock paths preserve discovery', (t) => {
|
|
const f = fixture(t);
|
|
check(
|
|
f.run(
|
|
{},
|
|
'type(){ if [[ "$*" == "-P flock" ]]; then return 1; fi; builtin type "$@"; }; ' +
|
|
f.command,
|
|
),
|
|
);
|
|
fs.rmSync(f.cache);
|
|
fs.writeFileSync(path.join(f.bin, 'flock'), '#!/bin/bash\nexit 64\n', {
|
|
mode: 0o755,
|
|
});
|
|
check(f.run());
|
|
fs.rmSync(f.cache);
|
|
fs.rmSync(f.lock, { force: true });
|
|
const victim = path.join(f.root, 'victim');
|
|
fs.writeFileSync(victim, 'untouched');
|
|
fs.symlinkSync(victim, f.lock);
|
|
check(f.run());
|
|
assert.equal(fs.readFileSync(victim, 'utf8'), 'untouched');
|
|
assert.equal(f.count(), 3);
|
|
});
|
|
test('failed refresh releases lock and does not publish a success', (t) => {
|
|
const f = fixture(t);
|
|
assert.equal(f.run({ FAIL: '1' }).status, 17);
|
|
assert.equal(fs.existsSync(f.cache), false);
|
|
check(f.run());
|
|
assert.equal(f.count(), 2);
|
|
});
|
|
test('refresh leaves the caller file descriptor and umask unchanged', (t) => {
|
|
const f = fixture(t);
|
|
const sentinel = path.join(f.root, 'fd');
|
|
const mask = path.join(f.root, 'mask');
|
|
const r = f.run(
|
|
{ SENTINEL: sentinel, UMASK_FILE: mask },
|
|
'umask 022; exec 9> "$SENTINEL"; ' +
|
|
f.command +
|
|
' >/dev/null; printf preserved >&9; umask',
|
|
);
|
|
assert.equal(r.status, 0, r.stderr);
|
|
assert.equal(r.stdout.trim(), '0022');
|
|
assert.equal(fs.readFileSync(sentinel, 'utf8'), 'preserved');
|
|
assert.equal(fs.readFileSync(mask, 'utf8').trim(), '0022');
|
|
});
|