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; } });