Spaces:
Running
Running
File size: 5,503 Bytes
b8cc2bf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | /**
* Integration tests for buffer.worker.ts (v4 centralized multi-layer data store).
*
* Ensures the BufferWorker:
* - Loads and responds to INIT with the v4 layer config
* - Accepts WRITE to VAD layers and responds to HAS_SPEECH / GET_SILENCE_TAIL
* - Resets state on RESET
*
* Run: npm test
*/
import '@vitest/web-worker';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import type { BufferWorkerConfig } from './types';
function defaultConfig(): BufferWorkerConfig {
return {
sampleRate: 16000,
layers: {
audio: { hopSamples: 1, entryDimension: 1, maxDurationSec: 30 },
mel: { hopSamples: 160, entryDimension: 128, maxDurationSec: 30 },
energyVad: { hopSamples: 1280, entryDimension: 1, maxDurationSec: 30 },
inferenceVad: { hopSamples: 256, entryDimension: 1, maxDurationSec: 30 },
},
};
}
function sendRequest(
worker: Worker,
type: string,
payload: any,
id: number
): Promise<{ type: string; id?: number; payload?: any }> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error(`Worker ${type} timed out`)), 5000);
const handler = (e: MessageEvent) => {
const data = e.data as { type: string; id?: number; payload?: any };
if (data.type === 'ERROR' && data.id === id) {
clearTimeout(timeout);
worker.removeEventListener('message', handler);
reject(new Error(data.payload));
return;
}
if (data.id === id || (type === 'INIT' && data.type === 'INIT')) {
clearTimeout(timeout);
worker.removeEventListener('message', handler);
resolve(data);
return;
}
};
worker.addEventListener('message', handler);
worker.postMessage({ type, payload, id });
});
}
describe('buffer.worker', () => {
let worker: Worker;
let nextId: number;
beforeEach(() => {
worker = new Worker(new URL('./buffer.worker.ts', import.meta.url), {
type: 'module',
});
nextId = 1;
});
afterEach(() => {
worker.terminate();
});
it('should load without errors', async () => {
const errPromise = new Promise<ErrorEvent>((resolve) => {
worker.onerror = (e) => resolve(e as ErrorEvent);
});
const initPromise = sendRequest(worker, 'INIT', defaultConfig(), nextId++);
const result = await Promise.race([
initPromise.then(() => 'ok'),
errPromise.then((e) => {
throw new Error(`Worker load failed: ${e.message}`);
}),
]);
expect(result).toBe('ok');
});
it('should respond to INIT with success', async () => {
const response = await sendRequest(worker, 'INIT', defaultConfig(), nextId++);
expect(response.type).toBe('INIT');
expect(response.payload?.success).toBe(true);
});
it('should return no speech and zero silence tail before any write', async () => {
await sendRequest(worker, 'INIT', defaultConfig(), nextId++);
const hasSpeech = await sendRequest(
worker,
'HAS_SPEECH',
{ layer: 'energyVad', startSample: 0, endSample: 1280, threshold: 0.3 },
nextId++
);
expect(hasSpeech.type).toBe('HAS_SPEECH');
expect(hasSpeech.payload?.hasSpeech).toBe(false);
const silence = await sendRequest(
worker,
'GET_SILENCE_TAIL',
{ layer: 'energyVad', threshold: 0.3 },
nextId++
);
expect(silence.type).toBe('GET_SILENCE_TAIL');
expect(silence.payload?.durationSec).toBe(0);
});
it('should report speech after writing above-threshold values to energyVad', async () => {
await sendRequest(worker, 'INIT', defaultConfig(), nextId++);
worker.postMessage({
type: 'WRITE',
payload: { layer: 'energyVad', data: [0.9] },
});
await new Promise((r) => setTimeout(r, 50));
const hasSpeech = await sendRequest(
worker,
'HAS_SPEECH',
{ layer: 'energyVad', startSample: 0, endSample: 1280, threshold: 0.3 },
nextId++
);
expect(hasSpeech.payload?.hasSpeech).toBe(true);
expect(hasSpeech.payload?.maxProb).toBeGreaterThanOrEqual(0.3);
});
it('should return silence tail duration after writing silence', async () => {
await sendRequest(worker, 'INIT', defaultConfig(), nextId++);
worker.postMessage({ type: 'WRITE', payload: { layer: 'energyVad', data: [0.1] } });
worker.postMessage({ type: 'WRITE', payload: { layer: 'energyVad', data: [0.1] } });
await new Promise((r) => setTimeout(r, 50));
const silence = await sendRequest(
worker,
'GET_SILENCE_TAIL',
{ layer: 'energyVad', threshold: 0.3 },
nextId++
);
expect(silence.payload?.durationSec).toBeGreaterThan(0);
});
it('should respond to RESET with success', async () => {
await sendRequest(worker, 'INIT', defaultConfig(), nextId++);
const response = await sendRequest(worker, 'RESET', undefined, nextId++);
expect(response.type).toBe('RESET');
expect(response.payload?.success).toBe(true);
});
});
|