Files
gnome-jarvis/test/qvac-ownership.test.js
T
snxraven d47e9e260f
Rolling release / release (push) Failing after 1m49s
Updates
2026-09-12 19:36:06 -04:00

66 lines
3.2 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { Agent, acquireQvac, releaseQvac, closeQvac, qvacStatus } from '../daemon/qvac-master.js';
test('concurrent master acquisition loads once and failed acquisition does not leak owners', async () => {
const original = { resources: Agent.engine.resources, load: Agent.engine.load, close: Agent.engine.close };
let loads = 0;
Agent.engine.resources = async () => ({ gpus: [{}] });
Agent.engine.load = async () => { loads++; return { device: 'gpu' }; };
Agent.engine.close = async () => {};
try {
await Promise.all([acquireQvac(), acquireQvac()]);
assert.equal(loads, 1); assert.equal(qvacStatus().owners, 2);
releaseQvac(); releaseQvac(); await closeQvac();
Agent.engine.resources = async () => { throw new Error('discovery failed'); };
await assert.rejects(acquireQvac(), /discovery failed/);
assert.equal(qvacStatus().owners, 0);
Agent.engine.resources = async () => ({ gpus: [{}] });
await acquireQvac(); assert.equal(loads, 2); releaseQvac();
} finally { await closeQvac(); Object.assign(Agent.engine, original); }
});
test('concurrent auxiliary loads share one model and failures permit retry', async () => {
const { loadAuxiliaryModel, unloadAuxiliaryModel } = await import('../daemon/qvac-master.js');
const original = Agent.engine.ensureInit;
let loads = 0, unloads = 0, fail = false;
Agent.engine.ensureInit = async () => ({
loadModel: async () => { loads++; if (fail) throw new Error('load failed'); return `aux-${loads}`; },
unloadModel: async () => { unloads++; },
});
try {
const ids = await Promise.all([loadAuxiliaryModel('shared-asr', {}, 'whispercpp-transcription'), loadAuxiliaryModel('shared-asr', {}, 'whispercpp-transcription')]);
assert.equal(loads, 1);
assert.equal(ids[0], ids[1]);
await unloadAuxiliaryModel(ids[0]);
assert.equal(unloads, 0, 'the second user still owns the shared model');
await unloadAuxiliaryModel(ids[1]);
assert.equal(unloads, 1);
fail = true;
const failures = await Promise.allSettled([loadAuxiliaryModel('retry-asr'), loadAuxiliaryModel('retry-asr')]);
assert.ok(failures.every((result) => result.status === 'rejected'));
assert.equal(loads, 2);
fail = false;
await unloadAuxiliaryModel(await loadAuxiliaryModel('retry-asr'));
assert.equal(loads, 3);
assert.equal(qvacStatus().auxiliaryModels, 0);
} finally { Agent.engine.ensureInit = original; }
});
test('Whisper requests GPU through its own context configuration', async () => {
const { QvacVoiceAdapter } = await import('../daemon/voice-adapters.js');
const original = Agent.engine.ensureInit;
let loaded;
Agent.engine.ensureInit = async () => ({
loadModel: async (options) => { loaded = options; return 'gpu-asr'; },
unloadModel: async () => {},
});
const adapter = new QvacVoiceAdapter({ role: 'asr' });
try {
await adapter.start();
assert.equal(loaded.modelType, 'whispercpp-transcription');
assert.equal(loaded.modelConfig.contextParams.use_gpu, true);
assert.equal(loaded.modelConfig.gpu_layers, undefined);
} finally { await adapter.stop(); Agent.engine.ensureInit = original; }
});