"""Compile actual microphone, queue and receive diagnostics with scripted inputs.""" from pathlib import Path import subprocess import tempfile root = Path(__file__).resolve().parents[1] app = root / 'ql-application/threadx/interface/custom_app' voice = (app / 'ws_talkingq.c').read_text() mic = (app / 'vad_det.c').read_text() def function(source, signature): start = source.index(signature) opening = source.index('{', start) depth, end = 1, opening + 1 while depth: depth += (source[end] == '{') - (source[end] == '}') end += 1 return source[start:end] + '\n' harness = r''' #include #include #include #include #include #define VAD_FRAME_BYTES 640 #define DEVICE_ID_SIZE 32 #define SESSION_ID_SIZE 33 #define RLOGE(...) ((void)0) typedef int rws_socket; static int g_mic_working; static int g_state, ring_result = 640, fail_alloc, queue_result, dispatched; static unsigned int conversation_round = 7; static int active_session_valid = 1; static char sessionId[33] = "session", deviceId[32] = "device"; static void *ring_sender; static uint64_t now = 1000; static int mutex_locked; static uint64_t hal_os_up_time_ms(void) { return now; } static void conversation_lock(void) { assert(!mutex_locked); mutex_locked = 1; } static void conversation_unlock(void) { assert(mutex_locked); mutex_locked = 0; } static int hal_os_rb_write(void *ring, const void *data, int len, unsigned int timeout) { (void)ring; (void)data; (void)len; assert(timeout == 25); return ring_result; } static void *test_malloc(size_t size) { return fail_alloc ? NULL : malloc(size); } static int talkingq_ws_send_bin(uint8_t *data, int len) { uint32_t seq; assert(len >= DEVICE_ID_SIZE + SESSION_ID_SIZE + 12); memcpy(&seq, data + DEVICE_ID_SIZE + SESSION_ID_SIZE, 4); assert(seq == 9); return queue_result; } typedef struct { uint32_t sequenceNumber, packetType, dataSize; uint8_t *data; } AudioPacket; static int conversation_accept_voice_message(const char *text) { return strstr(text, "old") == NULL; } static void conversation_log_error_message(const char *text, unsigned int len) { (void)text; (void)len; } static void onmessage_text_locked(rws_socket socket, const char *text, unsigned int len) { (void)socket; (void)text; (void)len; assert(mutex_locked); dispatched++; } ''' start = voice.index('static struct {') end = voice.index('} voice_diag;', start) + len('} voice_diag;') harness += voice[start:end] + '\n' start = mic.index('static struct {') end = mic.index('extern int audio_packet_type;', start) harness += mic[start:end] harness += '#define malloc test_malloc\n' harness += function(voice, 'int sendRecordingAudioPacket(AudioPacket *packet) {') harness += '#undef malloc\n' harness += function(voice, 'static void onmessage_text(rws_socket socket,') harness += function(voice, 'static void onmessage_bin(rws_socket socket,') harness += r''' int main(void) { uint8_t pcm[640] = {0}; AudioPacket packet = {9, 0, 4, pcm}; mic_diag.started_ms = now; mic_diag_read(pcm, 640, 0); assert(mic_diag.reads == 1 && mic_diag.zero_frames == 1 && mic_diag.peak == 0); /* Full-scale negative input must avoid int16 absolute-value overflow. */ pcm[0] = 0; pcm[1] = 128; mic_diag_read(pcm, 640, 0); assert(mic_diag.peak == 32768 && mic_diag.abs_sum == 32768 && mic_diag.samples == 640); mic_diag_read(pcm, 0, -1); assert(mic_diag.short_reads == 1 && mic_diag.read_errors == 1 && mic_diag.zero_frames == 1); mic_diag_read(pcm, 10000, 0); /* Diagnostic sampling stays inside the buffer. */ assert(mic_diag.samples == 960 && mic_diag.short_reads == 2); ring_result = 640; assert(mic_diag_write(pcm, 640) == 640 && !mic_diag.ring_drops); ring_result = -1; assert(mic_diag_write(pcm, 640) == -1); ring_result = 320; assert(mic_diag_write(pcm, 640) == 320); assert(mic_diag.ring_drops == 2 && mic_diag.ring_drop_bytes == 960); mic_diag_log("test"); queue_result = 0; assert(sendRecordingAudioPacket(&packet) == 0); assert(voice_diag.tx_packets == 1 && voice_diag.tx_bytes == 4 && !voice_diag.tx_failed); queue_result = -1; assert(sendRecordingAudioPacket(&packet) == -1); fail_alloc = 1; assert(sendRecordingAudioPacket(&packet) == -1); assert(voice_diag.tx_packets == 1 && voice_diag.tx_bytes == 4 && voice_diag.tx_failed == 2); onmessage_text(0, "START_ACK", 9); onmessage_text(0, "END_ACK", 7); onmessage_text(0, "unknown backend response", 24); onmessage_text(0, "old TTS_START", 13); assert(voice_diag.start_ack == 1 && voice_diag.end_ack == 1 && voice_diag.rx_text == 4); assert(dispatched == 3 && !mutex_locked); now = 2000; onmessage_bin(0, pcm, 640); assert(voice_diag.rx_bin == 1 && voice_diag.last_rx_ms == now && !mutex_locked); puts("PASS: microphone silence/level/short reads/errors, PCM drops, queue failure/allocation failure, ACK visibility, unknown/rejected replies, ignored binary replies"); } ''' with tempfile.TemporaryDirectory(prefix='banban-voice-diag-') as directory: cfile = Path(directory) / 'diag.c' exe = Path(directory) / 'diag-test' cfile.write_text(harness) subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', '-fsanitize=address,undefined', str(cfile), '-o', str(exe)], check=True) subprocess.run([str(exe)], check=True)