mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-29 01:56:37 +08:00
169 lines
6.7 KiB
JavaScript
169 lines
6.7 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 { spawnSync } = require('node:child_process');
|
|
|
|
function fixture(t, language) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-lazy-notify-'));
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const extension = language === 'python' ? 'py' : 'js';
|
|
fs.copyFileSync(`shell/preload/sitecustomize.${extension}`, path.join(root, `sitecustomize.${extension}`));
|
|
fs.writeFileSync(path.join(root, `env.${extension}`), '');
|
|
fs.writeFileSync(path.join(root, 'before.sh'), 'export FROM_SHELL=before\n');
|
|
fs.writeFileSync(path.join(root, 'before.js'), 'process.env.FROM_LANGUAGE = "javascript";\n');
|
|
fs.writeFileSync(path.join(root, 'task_before.py'), 'import os\nos.environ["FROM_LANGUAGE"] = "python"\n');
|
|
fs.writeFileSync(path.join(root, 'client.js'), 'module.exports = { getEnvs: () => "client-ready" };\n');
|
|
fs.copyFileSync('shell/preload/client.py', path.join(root, 'client.py'));
|
|
fs.copyFileSync('shell/preload/esm-loader.mjs', path.join(root, 'esm-loader.mjs'));
|
|
const env = {
|
|
...process.env,
|
|
PYTHONPATH: root,
|
|
PREV_PYTHONPATH: '',
|
|
NODE_OPTIONS: '',
|
|
PREV_NODE_OPTIONS: '',
|
|
QL_NODE_GLOBAL_PATH: '',
|
|
file_task_before: path.join(root, 'before.sh'),
|
|
file_task_before_js: path.join(root, 'before.js'),
|
|
dir_scripts: root,
|
|
task_before: 'export FROM_COMMAND=command',
|
|
envParam: 'ACCOUNTS',
|
|
numParam: '2-3',
|
|
ACCOUNTS: 'first&second&third',
|
|
};
|
|
const write = (name, text) => fs.writeFileSync(path.join(root, name), text);
|
|
const run = (body, extensionOverride = extension) => {
|
|
const entry = path.join(root, `entry.${extensionOverride}`);
|
|
write(path.basename(entry), body);
|
|
const python = language === 'python';
|
|
const result = spawnSync(python ? 'python3' : process.execPath,
|
|
python ? [entry] : ['--require', path.join(root, 'sitecustomize.js'), entry],
|
|
{ cwd: root, env, encoding: 'utf8', timeout: 15000 });
|
|
assert.equal(result.status, 0, result.stderr + result.stdout);
|
|
return result.stdout;
|
|
};
|
|
return { root, env, write, run };
|
|
}
|
|
|
|
for (const extension of ['js', 'mjs']) {
|
|
test(`${extension} startup preserves hooks, environment selection and client without importing notifications`, (t) => {
|
|
const f = fixture(t, 'javascript');
|
|
f.write('__ql_notify__.js', 'throw Error("notification dependencies must not load at startup");');
|
|
f.run(`
|
|
const assert = ${extension === 'mjs' ? '(await import("node:assert/strict")).default' : 'require("node:assert/strict")'};
|
|
assert.equal(process.env.FROM_SHELL, 'before');
|
|
assert.equal(process.env.FROM_LANGUAGE, 'javascript');
|
|
assert.equal(process.env.FROM_COMMAND, 'command');
|
|
assert.equal(process.env.ACCOUNTS, 'second&third');
|
|
assert.equal(QLAPI.getEnvs(), 'client-ready');
|
|
assert.equal(typeof QLAPI.notify, 'function');
|
|
`, extension);
|
|
});
|
|
}
|
|
|
|
test('JavaScript first notification imports once and preserves arguments, this, promises and errors', (t) => {
|
|
const f = fixture(t, 'javascript');
|
|
f.write('__ql_notify__.js', `
|
|
global.notificationLoads = (global.notificationLoads || 0) + 1;
|
|
exports.sendNotify = function (...args) {
|
|
if (args[0] === 'fail') throw new Error('send failed');
|
|
global.notificationResult = Promise.resolve({ args, receiver: this });
|
|
return global.notificationResult;
|
|
};
|
|
`);
|
|
f.run(`
|
|
const assert = require('node:assert/strict');
|
|
assert.equal(global.notificationLoads, undefined);
|
|
(async () => {
|
|
const options = { channel: 'fixture' };
|
|
const promise = QLAPI.notify('title', 'content', options);
|
|
assert.equal(promise, global.notificationResult);
|
|
const result = await promise;
|
|
assert.deepEqual(result.args, ['title', 'content', options]);
|
|
assert.equal(result.receiver, QLAPI);
|
|
await QLAPI.notify('second');
|
|
assert.equal(global.notificationLoads, 1);
|
|
assert.throws(() => QLAPI.notify('fail'), /send failed/);
|
|
})().catch(error => { console.error(error); process.exitCode = 1; });
|
|
`);
|
|
});
|
|
|
|
test('JavaScript reports import failures on notify and permits a later retry', (t) => {
|
|
const f = fixture(t, 'javascript');
|
|
f.write('__ql_notify__.js', `
|
|
if (!process.env.NOTIFY_READY) throw Error('notification dependency unavailable');
|
|
exports.sendNotify = (...args) => args;
|
|
`);
|
|
f.run(`
|
|
const assert = require('node:assert/strict');
|
|
assert.equal(QLAPI.getEnvs(), 'client-ready');
|
|
assert.throws(() => QLAPI.notify('first'), /notification dependency unavailable/);
|
|
process.env.NOTIFY_READY = '1';
|
|
assert.deepEqual(QLAPI.notify('retry'), ['retry']);
|
|
`);
|
|
});
|
|
|
|
test('Python startup preserves hooks, environment selection and client without importing notifications', (t) => {
|
|
const f = fixture(t, 'python');
|
|
f.write('__ql_notify__.py', 'raise RuntimeError("notification dependencies must not load at startup")\n');
|
|
f.run(`
|
|
import os, sys
|
|
assert os.environ['FROM_SHELL'] == 'before'
|
|
assert os.environ['FROM_LANGUAGE'] == 'python'
|
|
assert os.environ['FROM_COMMAND'] == 'command'
|
|
assert os.environ['ACCOUNTS'] == 'second&third'
|
|
assert '__ql_notify__' not in sys.modules
|
|
assert callable(QLAPI.notify)
|
|
assert callable(QLAPI.getEnvs)
|
|
`);
|
|
});
|
|
|
|
test('Python first notification imports once and preserves args, kwargs, return values and errors', (t) => {
|
|
const f = fixture(t, 'python');
|
|
f.write('__ql_notify__.py', `
|
|
import builtins
|
|
builtins.notification_loads = getattr(builtins, 'notification_loads', 0) + 1
|
|
def send(*args, **kwargs):
|
|
if args[0] == 'fail':
|
|
raise ValueError('send failed')
|
|
return args, kwargs
|
|
`);
|
|
f.run(`
|
|
import builtins, sys
|
|
assert '__ql_notify__' not in sys.modules
|
|
assert QLAPI.notify('title', 'content', channel='fixture') == (('title', 'content'), {'channel': 'fixture'})
|
|
assert QLAPI.notify('second') == (('second',), {})
|
|
assert builtins.notification_loads == 1
|
|
try:
|
|
QLAPI.notify('fail')
|
|
except ValueError as error:
|
|
assert str(error) == 'send failed'
|
|
else:
|
|
raise AssertionError('notification exception was swallowed')
|
|
`);
|
|
});
|
|
|
|
test('Python reports import failures on notify and permits a later retry', (t) => {
|
|
const f = fixture(t, 'python');
|
|
f.write('__ql_notify__.py', `
|
|
import os
|
|
if not os.environ.get('NOTIFY_READY'):
|
|
raise RuntimeError('notification dependency unavailable')
|
|
def send(*args, **kwargs):
|
|
return args
|
|
`);
|
|
f.run(`
|
|
import os, sys
|
|
assert '__ql_notify__' not in sys.modules
|
|
try:
|
|
QLAPI.notify('first')
|
|
except RuntimeError as error:
|
|
assert str(error) == 'notification dependency unavailable'
|
|
else:
|
|
raise AssertionError('import exception was swallowed')
|
|
os.environ['NOTIFY_READY'] = '1'
|
|
assert QLAPI.notify('retry') == ('retry',)
|
|
`);
|
|
});
|