CI / CI configuration security checks (push) Successful in 3s
CI / Capabilities and shell quality (push) Successful in 15s
CI / Python tests and compilation (push) Successful in 23s
CI / QMD unit tests (push) Failing after 11s
CI / Build and inspect generated packages (push) Successful in 17s
CI / PowerShell tests on Ubuntu (push) Successful in 1m2s
CI / Installed client configuration smoke tests (push) Successful in 1m7s
CI / Bash integration and installation guards (push) Successful in 1m17s
138 lines
7.3 KiB
JavaScript
138 lines
7.3 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { cpSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { createRequire } from 'node:module';
|
|
import { tmpdir } from 'node:os';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import * as YAML from './fixtures/yaml-shim.mjs';
|
|
import { collectionName, deviceEnv, idleMinutes, qmdConfigPath, qmdPackageDir, writePrivateJson, writeTextAtomic, readJson, runQmdCli, MODELS } from '../../shared/qmd/qmd-lib.mjs';
|
|
import { setModels, unsetModels, previousModels, restoreModels, setCollectionIgnore } from '../../shared/qmd/qmd-config.mjs';
|
|
|
|
for (const layout of ['nested', 'hoisted']) {
|
|
test(`config CLI and YAML shim load ${layout} dependencies`, (t) => {
|
|
const root = mkdtempSync(join(tmpdir(), 'qmd dependencies '));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const pkg = join(root, 'node_modules', '@tobilu', 'qmd');
|
|
mkdirSync(pkg, { recursive: true });
|
|
const require = createRequire(join(qmdPackageDir(), 'package.json'));
|
|
const source = dirname(require.resolve('yaml/package.json'));
|
|
cpSync(source, join(layout === 'nested' ? pkg : root, 'node_modules', 'yaml'), { recursive: true });
|
|
const env = { ...process.env, QMD_PACKAGE_DIR: pkg, QMD_CONFIG_DIR: join(root, 'config') };
|
|
const output = execFileSync(process.execPath, [fileURLToPath(new URL('../../shared/qmd/qmd-config.mjs', import.meta.url)), 'set-models'], { env, encoding: 'utf8' });
|
|
assert.deepEqual(JSON.parse(output), { previous: null });
|
|
assert.deepEqual(YAML.default.parse(readFileSync(join(env.QMD_CONFIG_DIR, 'index.yml'), 'utf8')).models, MODELS);
|
|
const shim = new URL('./fixtures/yaml-shim.mjs', import.meta.url).href;
|
|
const parsed = execFileSync(process.execPath, ['--input-type=module', '-e', `import YAML from ${JSON.stringify(shim)}; console.log(JSON.stringify(YAML.parse('answer: 42')));`], { env, encoding: 'utf8' });
|
|
assert.deepEqual(JSON.parse(parsed), { answer: 42 });
|
|
});
|
|
}
|
|
|
|
test('collection name is stable, prefixed, and path-safe', () => {
|
|
const a = collectionName('/tmp/My Repo/ü');
|
|
assert.match(a, /^repo-[0-9a-f]{12}$/);
|
|
assert.equal(a, collectionName('/tmp/My Repo/ü'));
|
|
assert.notEqual(a, collectionName('/tmp/My Repo/u'));
|
|
});
|
|
|
|
test('device env forces CPU or enables GPU auto', () => {
|
|
assert.equal(deviceEnv('cpu', { QMD_LLAMA_GPU: 'cuda' }).QMD_FORCE_CPU, '1');
|
|
assert.equal(deviceEnv('cpu', { QMD_LLAMA_GPU: 'cuda' }).QMD_LLAMA_GPU, undefined);
|
|
assert.equal(deviceEnv('gpu', { QMD_FORCE_CPU: '1' }).QMD_LLAMA_GPU, 'auto');
|
|
assert.equal(deviceEnv('gpu', { QMD_FORCE_CPU: '1' }).QMD_FORCE_CPU, undefined);
|
|
});
|
|
|
|
test('device env never narrows GPUs or threads', () => {
|
|
for (const device of ['gpu', 'cpu']) {
|
|
const env = deviceEnv(device, {});
|
|
for (const key of ['CUDA_VISIBLE_DEVICES', 'GGML_VK_VISIBLE_DEVICES', 'HIP_VISIBLE_DEVICES', 'OMP_NUM_THREADS']) assert.equal(env[key], undefined);
|
|
}
|
|
});
|
|
|
|
test('idle minutes default to 30 and reject nonsense', () => {
|
|
assert.equal(idleMinutes({}), 30);
|
|
assert.equal(idleMinutes({ QMD_IDLE_MINUTES: '5' }), 5);
|
|
assert.equal(idleMinutes({ QMD_IDLE_MINUTES: '-1' }), 30);
|
|
});
|
|
|
|
test('config path honors QMD_CONFIG_DIR then XDG_CONFIG_HOME', () => {
|
|
assert.equal(qmdConfigPath({ QMD_CONFIG_DIR: '/c' }, '/h'), join('/c', 'index.yml'));
|
|
assert.equal(qmdConfigPath({ XDG_CONFIG_HOME: '/x' }, '/h'), join('/x', 'qmd', 'index.yml'));
|
|
assert.equal(qmdConfigPath({}, '/h'), join('/h', '.config', 'qmd', 'index.yml'));
|
|
});
|
|
|
|
test('private json round-trips', () => {
|
|
const file = join(mkdtempSync(join(tmpdir(), 'qmd-lib-')), 'a', 'b.json');
|
|
writePrivateJson(file, { x: 1 });
|
|
assert.deepEqual(readJson(file, null), { x: 1 });
|
|
assert.equal(readJson(file + '.missing', 'fallback'), 'fallback');
|
|
});
|
|
|
|
test('setModels keeps collections and comments, unsetModels removes only models', () => {
|
|
const original = '# mine\ncollections:\n notes:\n path: /n # keep\n';
|
|
const withModels = setModels(original, YAML);
|
|
assert.match(withModels, /# mine/);
|
|
assert.match(withModels, /path: \/n # keep/);
|
|
assert.match(withModels, new RegExp(MODELS.embed.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
const restored = unsetModels(withModels, YAML);
|
|
assert.doesNotMatch(restored, /models:/);
|
|
assert.match(restored, /path: \/n # keep/);
|
|
});
|
|
|
|
test('setModels on an empty file creates only the models block', () => {
|
|
const text = setModels('', YAML);
|
|
assert.match(text, /^models:/m);
|
|
});
|
|
|
|
test('runQmdCli runs the CLI entry of QMD_PACKAGE_DIR without a shell', () => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'qmd-pkg-'));
|
|
mkdirSync(join(dir, 'dist', 'cli'), { recursive: true });
|
|
writeFileSync(join(dir, 'dist', 'cli', 'qmd.js'), 'console.log(JSON.stringify(process.argv.slice(2)));\n');
|
|
const previous = process.env.QMD_PACKAGE_DIR;
|
|
process.env.QMD_PACKAGE_DIR = dir;
|
|
try {
|
|
assert.deepEqual(JSON.parse(runQmdCli(['search', 'a "b" & c'])), ['search', 'a "b" & c']);
|
|
} finally {
|
|
if (previous === undefined) delete process.env.QMD_PACKAGE_DIR; else process.env.QMD_PACKAGE_DIR = previous;
|
|
}
|
|
});
|
|
|
|
test('previousModels captures a user models block, null when absent', () => {
|
|
assert.deepEqual(previousModels('models:\n embed: mine\ncollections: {}\n', YAML), { embed: 'mine' });
|
|
assert.equal(previousModels('models: custom\n', YAML), 'custom');
|
|
assert.deepEqual(previousModels('models: [a, b]\n', YAML), ['a', 'b']);
|
|
assert.equal(previousModels('collections: {}\n', YAML), null);
|
|
assert.equal(previousModels('', YAML), null);
|
|
});
|
|
|
|
test('restoreModels writes the previous block back and keeps comments and other keys', () => {
|
|
const original = '# keep\ncollections:\n a: {}\nmodels:\n embed: mine\n';
|
|
const replaced = setModels(original, YAML);
|
|
const restored = restoreModels(replaced, YAML, { embed: 'mine' });
|
|
assert.match(restored, /# keep/);
|
|
assert.match(restored, /embed: mine/);
|
|
assert.match(restored, /collections:/);
|
|
assert.doesNotMatch(restored, /Qwen/);
|
|
});
|
|
|
|
test('writeTextAtomic replaces the file through a temp file in the same directory', () => {
|
|
const dir = join(mkdtempSync(join(tmpdir(), 'qmd-lib-')), 'cfg');
|
|
const file = join(dir, 'index.yml');
|
|
writeTextAtomic(file, 'a: 1\n');
|
|
writeTextAtomic(file, 'b: 2\n');
|
|
assert.equal(readFileSync(file, 'utf8'), 'b: 2\n');
|
|
assert.deepEqual(readdirSync(dir), ['index.yml']);
|
|
});
|
|
|
|
test('setCollectionIgnore writes the ignore list of one collection and keeps the rest', () => {
|
|
const original = '# mine\ncollections:\n notes:\n path: /n # keep\n repo-aaaaaaaaaaaa:\n path: /r\n includeByDefault: false\n';
|
|
const withIgnore = setCollectionIgnore(original, YAML, 'repo-aaaaaaaaaaaa', ['dist/**', 'a.ts']);
|
|
assert.match(withIgnore, /# mine/);
|
|
assert.match(withIgnore, /path: \/n # keep/);
|
|
assert.deepEqual(YAML.default.parse(withIgnore).collections['repo-aaaaaaaaaaaa'], { path: '/r', includeByDefault: false, ignore: ['dist/**', 'a.ts'] });
|
|
const cleared = setCollectionIgnore(withIgnore, YAML, 'repo-aaaaaaaaaaaa', []);
|
|
assert.deepEqual(YAML.default.parse(cleared).collections['repo-aaaaaaaaaaaa'], { path: '/r', includeByDefault: false });
|
|
assert.equal(setCollectionIgnore(original, YAML, 'repo-bbbbbbbbbbbb', ['x']), null, 'an unknown collection is not created');
|
|
});
|