mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-15 19:57:07 +08:00
fix: 修复任务生命周期与调度就绪,优化执行和构建开销 (#3069)
* 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
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
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 { spawnSync, spawn } = require('node:child_process');
|
||||
const helper = path.resolve('shell/node_path_cache.sh');
|
||||
function fixture(t) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-node-path-'));
|
||||
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'));
|
||||
const calls = path.join(root, 'calls');
|
||||
fs.writeFileSync(
|
||||
path.join(bin, 'pnpm'),
|
||||
'#!/bin/bash\necho call >> "$CALLS"\n[[ "${FAIL:-0}" == 1 ]] && exit 1\nprintf "%s\\n" "${ANSWER:-/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: calls,
|
||||
HELPER: helper,
|
||||
};
|
||||
const command = '. "$HELPER"; ql_get_node_global_path';
|
||||
const run = (extra = {}, code = command) =>
|
||||
spawnSync('/bin/bash', ['-uc', code], {
|
||||
cwd: root,
|
||||
env: { ...env, ...extra },
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const count = () =>
|
||||
fs.existsSync(calls)
|
||||
? fs.readFileSync(calls, 'utf8').trim().split('\n').length
|
||||
: 0;
|
||||
return { root, bin, env, command, run, count };
|
||||
}
|
||||
test('warm cache avoids pnpm, preserves spaces, and never evaluates cached text', (t) => {
|
||||
const f = fixture(t);
|
||||
const answer = '/test/path with spaces/$(touch injected)';
|
||||
assert.equal(f.run({ ANSWER: answer }).stdout.trim(), answer);
|
||||
assert.equal(f.run({ ANSWER: '/different' }).stdout.trim(), answer);
|
||||
assert.equal(f.count(), 1);
|
||||
assert.equal(fs.existsSync(path.join(f.root, 'injected')), false);
|
||||
});
|
||||
test('config, environment, cwd, and executable changes invalidate discovery', (t) => {
|
||||
const f = fixture(t);
|
||||
f.run();
|
||||
fs.writeFileSync(path.join(f.root, '.npmrc'), 'global-dir=/other\n');
|
||||
f.run();
|
||||
assert.equal(f.count(), 2);
|
||||
f.run({ npm_config_global_dir: '/third' });
|
||||
assert.equal(f.count(), 3);
|
||||
f.run({ PNPM_HOME: '/fourth' });
|
||||
assert.equal(f.count(), 4);
|
||||
fs.mkdirSync(path.join(f.root, 'child'));
|
||||
f.run({}, 'cd child; ' + f.command);
|
||||
assert.equal(f.count(), 5);
|
||||
f.run();
|
||||
const before = f.count();
|
||||
fs.appendFileSync(path.join(f.bin, 'pnpm'), '# upgrade\n');
|
||||
f.run();
|
||||
assert.equal(f.count(), before + 1);
|
||||
});
|
||||
test('expired or malformed records refresh and failed lookups are not cached', (t) => {
|
||||
const f = fixture(t);
|
||||
f.run();
|
||||
const file = path.join(
|
||||
f.env.dir_tmp,
|
||||
fs.readdirSync(f.env.dir_tmp).find((name) => name.endsWith('.cache')),
|
||||
);
|
||||
let lines = fs.readFileSync(file, 'utf8').split('\n');
|
||||
lines[1] = '0';
|
||||
fs.writeFileSync(file, lines.join('\n'));
|
||||
f.run();
|
||||
assert.equal(f.count(), 2);
|
||||
fs.writeFileSync(file, lines[0] + '\n08\n/incorrect\n');
|
||||
assert.equal(f.run().status, 0);
|
||||
assert.equal(f.count(), 3);
|
||||
fs.writeFileSync(file, lines[0]);
|
||||
assert.equal(f.run().status, 0);
|
||||
assert.equal(f.count(), 4);
|
||||
fs.rmSync(file);
|
||||
assert.equal(f.run({ FAIL: '1' }).status, 1);
|
||||
assert.equal(fs.existsSync(file), false);
|
||||
assert.equal(f.run().status, 0);
|
||||
assert.equal(f.count(), 6);
|
||||
});
|
||||
test('disabled or unavailable cache falls back without changing discovery output', (t) => {
|
||||
const f = fixture(t);
|
||||
for (let i = 0; i < 2; i++)
|
||||
assert.equal(
|
||||
f.run({ QL_NODE_PATH_CACHE: '0' }).stdout.trim(),
|
||||
'/test/global/node_modules',
|
||||
);
|
||||
assert.equal(f.count(), 2);
|
||||
assert.equal(fs.existsSync(f.env.dir_tmp), false);
|
||||
const blocked = path.join(f.root, 'file');
|
||||
fs.writeFileSync(blocked, '');
|
||||
assert.equal(
|
||||
f.run({ dir_tmp: blocked }).stdout.trim(),
|
||||
'/test/global/node_modules',
|
||||
);
|
||||
assert.equal(f.count(), 3);
|
||||
});
|
||||
test('concurrent refreshes publish complete records and do not overwrite symlink targets', async (t) => {
|
||||
const f = fixture(t);
|
||||
await Promise.all(
|
||||
Array.from(
|
||||
{ length: 8 },
|
||||
() =>
|
||||
new Promise((resolve, reject) => {
|
||||
const p = spawn('/bin/bash', ['-uc', f.command], {
|
||||
cwd: f.root,
|
||||
env: f.env,
|
||||
});
|
||||
let out = '';
|
||||
p.stdout.on('data', (c) => (out += c));
|
||||
p.once('error', reject);
|
||||
p.once('close', (code) => {
|
||||
try {
|
||||
assert.equal(code, 0);
|
||||
assert.equal(out.trim(), '/test/global/node_modules');
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}),
|
||||
),
|
||||
);
|
||||
const before = f.count();
|
||||
f.run();
|
||||
assert.equal(f.count(), before);
|
||||
const files = fs
|
||||
.readdirSync(f.env.dir_tmp)
|
||||
.filter((file) => file.endsWith('.cache'));
|
||||
assert.equal(files.length, 1);
|
||||
const file = path.join(f.env.dir_tmp, files[0]);
|
||||
fs.rmSync(file);
|
||||
const victim = path.join(f.root, 'victim');
|
||||
fs.writeFileSync(victim, 'unchanged');
|
||||
fs.symlinkSync(victim, file);
|
||||
assert.equal(f.run().status, 0);
|
||||
assert.equal(fs.readFileSync(victim, 'utf8'), 'unchanged');
|
||||
assert.equal(fs.lstatSync(file).isSymbolicLink(), false);
|
||||
});
|
||||
|
||||
test('task entry tolerates discovery failure with errexit enabled', (t) => {
|
||||
const f = fixture(t);
|
||||
const source = fs.readFileSync(path.resolve('shell/otask.sh'), 'utf8');
|
||||
const start = source.indexOf('append_node_dependency_path() {');
|
||||
const end = source.indexOf('\nenter_script_workdir()', start);
|
||||
const script =
|
||||
source.slice(start, end) +
|
||||
'\nappend_node_dependency_path; printf "continued:%s" "$NODE_PATH"';
|
||||
for (const dir of [path.dirname(helper), f.root]) {
|
||||
const r = f.run(
|
||||
{
|
||||
FAIL: '1',
|
||||
dir_shell: dir,
|
||||
dir_dep: '/legacy/deps',
|
||||
NODE_PATH: '/existing',
|
||||
},
|
||||
'set -e; ' + script,
|
||||
);
|
||||
assert.equal(r.status, 0, r.stderr);
|
||||
assert.equal(r.stdout, 'continued:/existing:/legacy/deps');
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user