// test_media_download_timeout.c — интеграционный тест watchdog-таймера скачивания // // Проверяет, что реальный uasync-таймер простоя срабатывает: // - загрузка с недостижимым автором → watchdog (100ms) → re-QUERY → суперноды // исчерпаны → done_cb(-1). // - загрузка с держателем, который «завис» (нет чанков) → фейловер → 3 попытки → // done_cb(-1). // // Таймауты укорочены (dl_stall_timeout_tb=100ms), фейковое время не используется — // гоняем реальный цикл uasync_poll. #include "media_delivery.h" #include "media_delivery_proto.h" #include "media_download.h" #include "media_index.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #include "../src/utun_instance.h" #include #include #include #include #include static int G_PASSED = 0, G_FAILED = 0, G_TOTAL = 0; #define TEST(n) do { G_TOTAL++; printf(" %-58s", n); fflush(stdout); } while(0) #define OK() do { G_PASSED++; printf("OK\n"); } while(0) #define FAIL(f,...) do { G_FAILED++; printf("FAIL: " f "\n", ##__VA_ARGS__); } while(0) static struct UASYNC* g_ua = NULL; static int g_done = 0, g_err = 0; static void make_uuid(uint8_t b[16]) { for (int i = 0; i < 16; i++) b[i] = (uint8_t)(rand() & 0xFF); } static void done_cb(void* a, int e) { (void)a; g_done = 1; g_err = e; printf(" [done_cb err=%d]\n", e); } static struct UTUN_INSTANCE* make_minimal(void) { struct UTUN_INSTANCE* inst = u_calloc(1, sizeof(*inst)); if (!inst) return NULL; inst->ua = g_ua; inst->node_id = 0xDEADBEEFDEADBEEFULL; sqlite3* db = NULL; sqlite3_open(":memory:", &db); inst->topo_sqlite_db = db; for (int i = 0; i < 32; i++) inst->my_ed25519_privkey[i] = (uint8_t)(rand() & 0xFF); memset(inst->my_ed25519_pubkey, 0xDD, 32); inst->md.inst = inst; inst->md.db = db; inst->md.self_node_id = inst->node_id; inst->md.initialized = 1; inst->md.dl_stall_timeout_tb = 1000; /* 100ms */ inst->md.dl_max_attempts = 3; return inst; } static void make_result(struct media_index_result* r, int num_blocks) { memset(r, 0, sizeof(*r)); make_uuid(r->media_id); r->num_blocks = num_blocks; r->file_size = 100 * num_blocks; r->block_size = 100; r->block_ids = u_calloc((size_t)num_blocks, 16); r->block_sigs = u_calloc((size_t)num_blocks, 64); for (int i = 0; i < num_blocks; i++) make_uuid(r->block_ids + i * 16); } static int poll_until_done(int max_ms) { int el = 0; while (!g_done && el < max_ms) { uasync_poll(g_ua, 5); el += 5; } return g_done; } int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); if (getenv("UTUN_TEST_DEBUG")) { debug_set_category_level_by_name("media", "trace"); debug_set_category_level_by_name("general", "info"); } srand(1); printf("=== test_media_download_timeout ===\n"); g_ua = uasync_create(); /* ── 1. недостижимый автор → watchdog → done_cb(-1) ── */ TEST("unreachable author → watchdog → fail"); { struct UTUN_INSTANCE* inst = make_minimal(); struct media_index_result r; make_result(&r, 1); g_done = 0; g_err = 0; int rc = media_download_start(inst, 0x1234, &r, "/tmp/utun_mdt_1.bin", "/tmp", 0xBADF00D00000001ULL, done_cb, NULL, NULL, NULL); if (rc != 0) { FAIL("start rc=%d", rc); } else if (poll_until_done(3000) && g_err != 0) OK(); else FAIL("done=%d err=%d", g_done, g_err); media_index_result_free(&r); /* нет активной загрузки после finish */ if (inst->md.downloads) { struct ll_entry* e = inst->md.downloads->head; int n = 0; while (e) { n++; e = e->next; } if (n != 0) FAIL("downloads leftover=%d", n); } sqlite3_close(inst->topo_sqlite_db); u_free(inst); } /* ── 2. зависший держатель → фейловер → re-QUERY → fail ── */ TEST("stalled holder → failover → fail"); { struct UTUN_INSTANCE* inst = make_minimal(); struct media_index_result r; make_result(&r, 1); g_done = 0; g_err = 0; int rc = media_download_start(inst, 0x1234, &r, "/tmp/utun_mdt_2.bin", "/tmp", 0xBADF00D00000002ULL, done_cb, NULL, NULL, NULL); if (rc != 0) { FAIL("start rc=%d", rc); } else { /* имитируем QUERY_RESP: автор сообщает, что держит блок */ struct media_pkt_query_resp_entry e; memset(&e, 0, sizeof(e)); e.node_id = 0xBADF00D00000002ULL; memcpy(e.block_id, r.block_ids, 16); uint8_t pkt[3 + sizeof(e)]; pkt[0] = MEDIA_SUBCMD_QUERY_RESP; uint16_t ne = 1; memcpy(pkt + 1, &ne, 2); memcpy(pkt + 3, &e, sizeof(e)); media_download_handle_query_resp(inst, pkt, sizeof(pkt)); /* держатель «завис» — чанков нет, watchdog сделает фейловер → попытки → fail */ if (poll_until_done(5000) && g_err != 0) OK(); else FAIL("done=%d err=%d (expected error after 3 attempts)", g_done, g_err); } media_index_result_free(&r); sqlite3_close(inst->topo_sqlite_db); u_free(inst); } printf("\n%d/%d passed, %d failed\n", G_PASSED, G_TOTAL, G_FAILED); if (g_ua) { uasync_destroy(g_ua, 0); g_ua = NULL; } return G_FAILED > 0 ? 1 : 0; }