- media_download: состояние блока (IDLE/CONN/REQ/RECV/WAIT/DONE/FAILED),
кандидаты-держатели (MD_MAX_HOLDERS_PER_BLOCK), фейловер + ретраи (MEDIA_DL_MAX_ATTEMPTS),
watchdog простоя (MEDIA_DL_STALL_TIMEOUT_TB) вместо фейкового времени
- media_delivery: настраиваемые dl_stall_timeout_tb / dl_max_attempts
- chat_msg: пометка ошибки скачивания в local_attrs ("st":"er") для UI
- tests: новый test_media_download_timeout (watchdog/фейловер), рефакторинг
test_media_delivery_download/full
- chatgui-android: состояние 'error' у вложений/видео (иконка + tap to retry)
124 lines
5.6 KiB
C
124 lines
5.6 KiB
C
// 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 <sqlite3.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
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;
|
|
}
|