3 changed files with 167 additions and 9 deletions
@ -0,0 +1,119 @@
|
||||
/* Настоящие transport/fragment/uasync, управляемый router без сетевых таймеров. */ |
||||
#define etcp_router_conn_get test_router_conn_get |
||||
#define etcp_router_conn_send test_router_conn_send |
||||
#define etcp_router_tx_stats test_router_tx_stats |
||||
#define etcp_router_send_q_has_room test_router_send_q_has_room |
||||
#define etcp_router_resume_receive test_router_resume_receive |
||||
#include "../src/call/call_video_transport.c" |
||||
#undef etcp_router_conn_get |
||||
#undef etcp_router_conn_send |
||||
#undef etcp_router_tx_stats |
||||
#undef etcp_router_send_q_has_room |
||||
#undef etcp_router_resume_receive |
||||
#include <assert.h> |
||||
#include <stdio.h> |
||||
|
||||
/* Один batch, без poll/timeouts: тест не может случайно продолжить TX таймером 50мс. */ |
||||
extern void process_immediate_queue(struct UASYNC *ua); |
||||
|
||||
static struct ETCP_ROUTER_CONN router; |
||||
static unsigned packets, received, room, resumed; |
||||
static uint64_t debt; |
||||
enum { FRAME_BYTES = 45001 }; |
||||
static uint8_t output[FRAME_BYTES]; |
||||
|
||||
struct ETCP_ROUTER_CONN *test_router_conn_get(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, uint8_t svc) { |
||||
(void)inst; (void)group; (void)peer; assert(svc == ETCP_RT_ID_CALL_VIDEO); return &router; |
||||
} |
||||
int test_router_conn_send(struct ETCP_ROUTER_CONN *conn, const uint8_t *data, size_t len, int mode) { |
||||
assert(conn == &router && !mode); |
||||
struct call_video_frame f; |
||||
assert(!call_video_fragment_parse(data, len, &f)); |
||||
assert(f.call_id == 7 && f.frame_id == 1 && f.size == FRAME_BYTES && f.offset == received); |
||||
assert(received + f.length <= sizeof(output)); |
||||
memcpy(output + received, f.data, f.length); received += f.length; packets++; return 0; |
||||
} |
||||
int test_router_tx_stats(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, uint8_t svc, |
||||
uint64_t now_us, struct ETCP_ROUTER_TX_STATS *out) { |
||||
(void)inst; (void)group; (void)peer; (void)svc; (void)now_us; |
||||
*out = (struct ETCP_ROUTER_TX_STATS){.inflight_bytes = debt, .ready = 1}; return 0; |
||||
} |
||||
int test_router_send_q_has_room(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, uint8_t svc) { |
||||
(void)inst; (void)group; (void)peer; (void)svc; return room; |
||||
} |
||||
void test_router_resume_receive(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, uint8_t svc) { |
||||
(void)inst; (void)group; (void)peer; (void)svc; resumed++; |
||||
} |
||||
|
||||
static struct call_video_transport *setup(struct call_session *s) { |
||||
struct call_video_transport *v = u_calloc(1, sizeof(*v)); assert(v); |
||||
v->session = s; s->video = v; |
||||
v->receiver = call_video_receiver_create(); assert(v->receiver); |
||||
v->have_peer = 1; v->local_revision = v->acknowledged_revision = 1; |
||||
v->local.encode[0] = (struct call_video_limit){1280, 720, 30, 1280 * 720 * 30, 4000000, CALL_VIDEO_BASELINE, 31}; |
||||
v->peer.decode[0] = v->local.encode[0]; |
||||
v->audio_end_pts = 1000000; |
||||
v->tx_count = 1; v->tx_bytes = FRAME_BYTES; |
||||
struct video_tx_frame *head = &v->tx[0]; |
||||
head->data = u_malloc(FRAME_BYTES); assert(head->data); |
||||
for (unsigned i = 0; i < FRAME_BYTES; i++) head->data[i] = (uint8_t)(i * 17 + i / 257); |
||||
head->started_tb = get_time_tb(); |
||||
head->frame = (struct call_video_frame){.call_id = 7, .frame_id = 1, .pts_us = 1000000, |
||||
.format = {1, 640, 480, 30, 0, CALL_VIDEO_H264, 31, 4000000, CALL_VIDEO_BASELINE}, .size = FRAME_BYTES}; |
||||
packets = received = resumed = 0; room = 1; debt = 0; |
||||
memset(output, 0, sizeof(output)); return v; |
||||
} |
||||
static void check_complete(struct call_video_transport *v) { |
||||
assert(packets == 38 && received == FRAME_BYTES && !v->tx_count && !v->tx_bytes && !v->flush_pending); |
||||
for (unsigned i = 0; i < FRAME_BYTES; i++) assert(output[i] == (uint8_t)(i * 17 + i / 257)); |
||||
} |
||||
static void cleanup(struct call_session *s) { |
||||
call_video_transport_stop(s); assert(!s->video && resumed == 1); |
||||
unsigned before = packets; |
||||
uasync_drain_immediate(s->ctx->inst->ua); |
||||
assert(packets == before); |
||||
assert(s->ctx->inst->ua->timer_alloc_count == s->ctx->inst->ua->timer_free_count); |
||||
} |
||||
int main(void) { |
||||
debug_set_level(DEBUG_LEVEL_WARN); |
||||
struct UASYNC *ua = uasync_create(); assert(ua); |
||||
struct UTUN_INSTANCE inst = {.ua = ua}; |
||||
struct call_ctx ctx = {.inst = &inst}; |
||||
struct call_session s = {.ctx = &ctx, .call_id = 7}; |
||||
router.send_q = queue_new(ua, 0, 0, 0, "video_flush_test"); assert(router.send_q); |
||||
|
||||
struct call_video_transport *v = setup(&s); |
||||
flush(v); assert(packets == 16 && v->flush_pending); |
||||
process_immediate_queue(ua); assert(packets == 32 && v->flush_pending); |
||||
process_immediate_queue(ua); check_complete(v); cleanup(&s); |
||||
puts("PASS: 45001-byte frame completes in bounded batches without audio or periodic timers; bytes preserved"); |
||||
|
||||
for (unsigned gate = 0; gate < 2; gate++) { |
||||
v = setup(&s); flush(v); |
||||
if (gate) debt = 1000000; else room = 0; |
||||
process_immediate_queue(ua); assert(packets == 16 && !v->flush_pending && v->flush_net_wait == 1); |
||||
process_immediate_queue(ua); assert(packets == 16); |
||||
room = 1; debt = 0; flush(v); uasync_drain_immediate(ua); check_complete(v); cleanup(&s); |
||||
} |
||||
puts("PASS: router room and inflight debt stop continuation without spinning; TX resumes exactly once"); |
||||
|
||||
v = setup(&s); v->audio_end_pts--; |
||||
flush(v); assert(!packets && !v->flush_pending); |
||||
call_video_audio_sent(&s, 1000000); uasync_drain_immediate(ua); check_complete(v); cleanup(&s); |
||||
v = setup(&s); v->acknowledged_revision = 0; |
||||
flush(v); assert(!packets && !v->flush_pending); |
||||
v->acknowledged_revision = 1; flush(v); uasync_drain_immediate(ua); check_complete(v); cleanup(&s); |
||||
puts("PASS: audio position and CAPS acknowledgement remain required"); |
||||
|
||||
v = setup(&s); flush(v); |
||||
call_video_audio_sent(&s, 1020000); assert(packets == 32); |
||||
process_immediate_queue(ua); check_complete(v); cleanup(&s); |
||||
puts("PASS: audio flush supersedes pending continuation without duplicate fragments"); |
||||
|
||||
v = setup(&s); flush(v); cleanup(&s); assert(packets == 16); |
||||
v = setup(&s); flush(v); discard_tx(v, "test_detach"); |
||||
assert(!v->tx_count && !v->flush_pending && v->need_key); |
||||
uasync_drain_immediate(ua); assert(packets == 16); cleanup(&s); |
||||
puts("PASS: stop and discard cancel pending callbacks before freeing their owner/data"); |
||||
queue_free(router.send_q); uasync_destroy(ua, 0); return 0; |
||||
} |
||||
Loading…
Reference in new issue