Browse Source
- test_media_delivery_sql: 15 tests (SQL tables, CRUD, packed structs) - test_media_delivery_download: 5 tests (chunk write/append, sig verify, assembly, cancel) - Fix: queue_new(NULL) returns NULL — pass inst->ua everywhere - Fix: queue index offset must be offsetof(struct, key_field) not 0 - Move struct media_download to header for test visibilitytopo_upd
6 changed files with 842 additions and 116 deletions
@ -0,0 +1,314 @@
|
||||
// test_media_delivery_download.c — тест скачивания блоков (chunk → done → assembly)
|
||||
//
|
||||
// Покрытие:
|
||||
// 1. media_download_start — регистрация в очереди, сбор суперузлов
|
||||
// 2. media_download_handle_chunk — запись чанка в .chunk_N
|
||||
// 3. media_download_handle_chunk — неизвестный media_id → игнор
|
||||
// 4. media_download_handle_done — проверка подписи, отметка validated
|
||||
// 5. media_download_handle_done — неверная подпись → сброс started/received
|
||||
// 6. media_download_handle_done — все блоки получены → сборка файла
|
||||
// 7. media_download_cancel — отправка CANCEL, done_cb с err=-2
|
||||
|
||||
#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 "../transport_layer/secure_channel.h" |
||||
#include "../routing_layer/topo_node_sqlite.h" |
||||
|
||||
#include <sqlite3.h> |
||||
#include <openssl/evp.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <sys/stat.h> |
||||
|
||||
#define TEMP_DIR "/tmp/test_mdl_dl_XXXXXX" |
||||
#define CHUNK_SIZE 100 |
||||
#define NUM_BLOCKS 3 |
||||
|
||||
static int g_passed = 0, g_failed = 0, g_total = 0; |
||||
static char g_temp_dir[256]; |
||||
static struct UASYNC* g_ua = NULL; |
||||
|
||||
#define TEST(name) do { g_total++; printf(" %-55s", name); } while(0) |
||||
#define OK() do { g_passed++; printf("OK\n"); } while(0) |
||||
#define FAIL(fmt, ...) do { g_failed++; printf("FAIL: " fmt "\n", ##__VA_ARGS__); } while(0) |
||||
|
||||
/* ── helpers ── */ |
||||
|
||||
static void make_uuid(uint8_t buf[16]) { for (int i = 0; i < 16; i++) buf[i] = (uint8_t)(rand() & 0xFF); } |
||||
|
||||
static void write_file(const char* path, const uint8_t* data, size_t len) { |
||||
FILE* f = fopen(path, "wb"); |
||||
if (f) { fwrite(data, 1, len, f); fclose(f); } |
||||
} |
||||
|
||||
static int file_exists(const char* path) { return access(path, F_OK) == 0; } |
||||
|
||||
static int file_size(const char* path) { |
||||
struct stat st; if (stat(path, &st) != 0) return -1; return (int)st.st_size; |
||||
} |
||||
|
||||
static void test_setup(void) { |
||||
snprintf(g_temp_dir, sizeof(g_temp_dir), "%s", TEMP_DIR); |
||||
if (!mkdtemp(g_temp_dir)) { fprintf(stderr, "mkdtemp failed\n"); exit(1); } |
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); |
||||
g_ua = uasync_create(); |
||||
} |
||||
|
||||
static void test_cleanup(void) { |
||||
char cmd[512]; snprintf(cmd, sizeof(cmd), "rm -rf %s", g_temp_dir); system(cmd); |
||||
if (g_ua) { uasync_destroy(g_ua, 0); g_ua = NULL; } |
||||
} |
||||
|
||||
/* ── create minimal UTUN_INSTANCE with media_delivery ── */ |
||||
|
||||
#include "../src/utun_instance.h" |
||||
|
||||
static struct UTUN_INSTANCE* make_minimal_instance(sqlite3* db) { |
||||
struct UTUN_INSTANCE* inst = u_calloc(1, sizeof(*inst)); |
||||
if (!inst) return NULL; |
||||
inst->ua = g_ua; |
||||
inst->node_id = 0xDEADBEEFDEADBEEFULL; |
||||
inst->topo_sqlite_db = db; |
||||
|
||||
/* generate Ed25519 key pair — just random bytes for test */ |
||||
for (int i = 0; i < 32; i++) inst->my_ed25519_privkey[i] = (uint8_t)(rand() & 0xFF); |
||||
memset(inst->my_ed25519_pubkey, 0xDD, 32); |
||||
|
||||
/* init media_delivery */ |
||||
inst->md.inst = inst; |
||||
inst->md.db = db; |
||||
inst->md.self_node_id = inst->node_id; |
||||
inst->md.initialized = 1; |
||||
|
||||
return inst; |
||||
} |
||||
|
||||
/* ── test cases ── */ |
||||
|
||||
static void test_download_chunk(void) { |
||||
TEST("download handle_chunk writes to temp file"); { |
||||
sqlite3* db = NULL; sqlite3_open(":memory:", &db); |
||||
struct UTUN_INSTANCE* inst = make_minimal_instance(db); |
||||
|
||||
inst->md.downloads = queue_new(inst->ua, 256, offsetof(struct media_download, media_id), 16, "md_dl_test"); |
||||
struct media_download dl_buf; memset(&dl_buf, 0, sizeof(dl_buf)); |
||||
make_uuid(dl_buf.media_id); |
||||
dl_buf.num_blocks = NUM_BLOCKS; |
||||
snprintf(dl_buf.dest_path, sizeof(dl_buf.dest_path), "%s/test_output.bin", g_temp_dir); |
||||
dl_buf.block_ids = u_malloc(NUM_BLOCKS * 16); |
||||
dl_buf.block_sigs = u_malloc(NUM_BLOCKS * 64); |
||||
for (int i = 0; i < NUM_BLOCKS; i++) make_uuid(dl_buf.block_ids + i * 16); |
||||
dl_buf.active = 1; |
||||
memcpy(dl_buf.ll.data, dl_buf.media_id, 16); |
||||
|
||||
struct ll_entry* qe = queue_entry_new(sizeof(struct media_download)); |
||||
if (qe) { memcpy(qe->data, &dl_buf, sizeof(dl_buf)); queue_data_put_with_index(inst->md.downloads, qe); } |
||||
|
||||
uint8_t chunk_data[256]; |
||||
for (int i = 0; i < 256; i++) chunk_data[i] = (uint8_t)(i & 0xFF); |
||||
|
||||
uint8_t pkt[MEDIA_BLOCK_CHUNK_HDR_SIZE + sizeof(chunk_data)]; |
||||
struct media_pkt_block_chunk* ch = (struct media_pkt_block_chunk*)pkt; |
||||
ch->subcmd = MEDIA_SUBCMD_BLOCK_CHUNK; |
||||
memcpy(ch->media_id, dl_buf.media_id, 16); |
||||
memcpy(ch->block_id, dl_buf.block_ids, 16); |
||||
ch->chunk = 0; ch->offset = 0; ch->data_len = (uint16_t)sizeof(chunk_data); |
||||
memcpy(pkt + MEDIA_BLOCK_CHUNK_HDR_SIZE, chunk_data, sizeof(chunk_data)); |
||||
|
||||
media_download_handle_chunk(inst, pkt, sizeof(pkt)); |
||||
|
||||
char tmp[1024]; snprintf(tmp, sizeof(tmp), "%s.chunk_0", dl_buf.dest_path); |
||||
int ok1 = file_exists(tmp) && file_size(tmp) == (int)sizeof(chunk_data); |
||||
|
||||
/* send second chunk with same block_id → appends */ |
||||
ch->offset = (uint32_t)sizeof(chunk_data); |
||||
media_download_handle_chunk(inst, pkt, sizeof(pkt)); |
||||
int ok2 = file_size(tmp) == 2 * (int)sizeof(chunk_data); |
||||
|
||||
if (ok1 && ok2) OK(); else FAIL("chunk: ok1=%d ok2=%d size=%d", ok1, ok2, file_size(tmp)); |
||||
|
||||
u_free(dl_buf.block_ids); u_free(dl_buf.block_sigs); |
||||
if (inst->md.downloads) queue_free(inst->md.downloads); |
||||
u_free(inst); sqlite3_close(db); |
||||
} |
||||
|
||||
TEST("download handle_chunk unknown media_id"); { |
||||
sqlite3* db = NULL; sqlite3_open(":memory:", &db); |
||||
struct UTUN_INSTANCE* inst = make_minimal_instance(db); |
||||
inst->md.downloads = queue_new(inst->ua, 256, offsetof(struct media_download, media_id), 16, "md_dl_test2"); |
||||
|
||||
uint8_t pkt[MEDIA_BLOCK_CHUNK_HDR_SIZE]; |
||||
struct media_pkt_block_chunk* ch = (struct media_pkt_block_chunk*)pkt; |
||||
ch->subcmd = MEDIA_SUBCMD_BLOCK_CHUNK; |
||||
make_uuid(ch->media_id); make_uuid(ch->block_id); |
||||
ch->chunk = 0; ch->offset = 0; ch->data_len = 0; |
||||
|
||||
media_download_handle_chunk(inst, pkt, sizeof(pkt)); |
||||
OK(); /* should not crash or create files */ |
||||
|
||||
if (inst->md.downloads) queue_free(inst->md.downloads); |
||||
u_free(inst); sqlite3_close(db); |
||||
} |
||||
} |
||||
|
||||
static void test_download_done(void) { |
||||
TEST("download handle_done sig ok + assembly"); { |
||||
sqlite3* db = NULL; sqlite3_open(":memory:", &db); |
||||
struct UTUN_INSTANCE* inst = make_minimal_instance(db); |
||||
inst->md.downloads = queue_new(inst->ua, 256, offsetof(struct media_download, media_id), 16, "md_dl_test3"); |
||||
|
||||
struct media_download dl; memset(&dl, 0, sizeof(dl)); |
||||
make_uuid(dl.media_id); |
||||
dl.num_blocks = 2; |
||||
snprintf(dl.dest_path, sizeof(dl.dest_path), "%s/out.bin", g_temp_dir); |
||||
dl.block_ids = u_malloc(16 * 2); dl.block_sigs = u_malloc(64 * 2); |
||||
make_uuid(dl.block_ids); make_uuid(dl.block_ids + 16); |
||||
memset(dl.block_sigs, 0x42, 64); memset(dl.block_sigs + 64, 0x43, 64); |
||||
dl.block_size = CHUNK_SIZE; dl.file_size = CHUNK_SIZE * 2; |
||||
dl.active = 1; |
||||
memcpy(dl.ll.data, dl.media_id, 16); |
||||
|
||||
struct ll_entry* qe = queue_entry_new(sizeof(struct media_download)); |
||||
if (!qe) { FAIL("queue_entry_new"); goto done3; } |
||||
memcpy(qe->data, &dl, sizeof(dl)); queue_data_put_with_index(inst->md.downloads, qe); |
||||
|
||||
/* write chunk files */ |
||||
uint8_t data0[CHUNK_SIZE]; memset(data0, 0xA0, CHUNK_SIZE); |
||||
uint8_t data1[CHUNK_SIZE]; memset(data1, 0xB1, CHUNK_SIZE); |
||||
{ |
||||
char c0[1024]; snprintf(c0, sizeof(c0), "%s.chunk_0", dl.dest_path); |
||||
char c1[1024]; snprintf(c1, sizeof(c1), "%s.chunk_1", dl.dest_path); |
||||
write_file(c0, data0, CHUNK_SIZE); write_file(c1, data1, CHUNK_SIZE); |
||||
} |
||||
|
||||
/* BLOCK_DONE for block 0 */ |
||||
{ |
||||
uint8_t pkt[MEDIA_BLOCK_DONE_SIZE]; |
||||
struct media_pkt_block_done* bd = (struct media_pkt_block_done*)pkt; |
||||
memset(pkt, 0, sizeof(pkt)); |
||||
bd->subcmd = MEDIA_SUBCMD_BLOCK_DONE; |
||||
memcpy(bd->media_id, dl.media_id, 16); |
||||
memcpy(bd->block_id, dl.block_ids, 16); |
||||
bd->chunk = 0; bd->total_size = CHUNK_SIZE; |
||||
memcpy(bd->block_sig, dl.block_sigs, 64); /* match expected */ |
||||
media_download_handle_done(inst, pkt, sizeof(pkt)); |
||||
} |
||||
/* BLOCK_DONE for block 1 */ |
||||
{ |
||||
uint8_t pkt[MEDIA_BLOCK_DONE_SIZE]; |
||||
struct media_pkt_block_done* bd = (struct media_pkt_block_done*)pkt; |
||||
memset(pkt, 0, sizeof(pkt)); |
||||
bd->subcmd = MEDIA_SUBCMD_BLOCK_DONE; |
||||
memcpy(bd->media_id, dl.media_id, 16); |
||||
memcpy(bd->block_id, dl.block_ids + 16, 16); |
||||
bd->chunk = 1; bd->total_size = CHUNK_SIZE; |
||||
memcpy(bd->block_sig, dl.block_sigs + 64, 64); |
||||
media_download_handle_done(inst, pkt, sizeof(pkt)); |
||||
} |
||||
|
||||
/* verify assembly */ |
||||
if (file_exists(dl.dest_path)) { |
||||
int sz = file_size(dl.dest_path); |
||||
if (sz == CHUNK_SIZE * 2) OK(); else FAIL("assembled size %d != %d", sz, CHUNK_SIZE * 2); |
||||
} else { FAIL("assembled file not created"); } |
||||
|
||||
done3: |
||||
u_free(dl.block_ids); u_free(dl.block_sigs); |
||||
if (inst->md.downloads) queue_free(inst->md.downloads); |
||||
u_free(inst); sqlite3_close(db); |
||||
} |
||||
|
||||
TEST("download handle_done bad sig → retry"); { |
||||
sqlite3* db = NULL; sqlite3_open(":memory:", &db); |
||||
struct UTUN_INSTANCE* inst = make_minimal_instance(db); |
||||
inst->md.downloads = queue_new(inst->ua, 256, offsetof(struct media_download, media_id), 16, "md_dl_test4"); |
||||
|
||||
struct media_download dl; memset(&dl, 0, sizeof(dl)); |
||||
make_uuid(dl.media_id); |
||||
dl.num_blocks = 1; |
||||
snprintf(dl.dest_path, sizeof(dl.dest_path), "%s/bad_sig.bin", g_temp_dir); |
||||
dl.block_ids = u_malloc(16); dl.block_sigs = u_malloc(64); |
||||
make_uuid(dl.block_ids); |
||||
memset(dl.block_sigs, 0x55, 64); /* expected sig */ |
||||
dl.block_size = CHUNK_SIZE; dl.file_size = CHUNK_SIZE; |
||||
dl.active = 1; |
||||
memcpy(dl.ll.data, dl.media_id, 16); |
||||
|
||||
struct ll_entry* qe = queue_entry_new(sizeof(struct media_download)); |
||||
if (!qe) { FAIL("queue_entry_new"); goto done4; } |
||||
memcpy(qe->data, &dl, sizeof(dl)); queue_data_put_with_index(inst->md.downloads, qe); |
||||
|
||||
/* write chunk file */ |
||||
uint8_t data[CHUNK_SIZE]; memset(data, 0xFF, CHUNK_SIZE); |
||||
char c0[1024]; snprintf(c0, sizeof(c0), "%s.chunk_0", dl.dest_path); |
||||
write_file(c0, data, CHUNK_SIZE); |
||||
|
||||
/* send BLOCK_DONE with WRONG signature */ |
||||
uint8_t pkt[MEDIA_BLOCK_DONE_SIZE]; |
||||
struct media_pkt_block_done* bd = (struct media_pkt_block_done*)pkt; |
||||
memset(pkt, 0, sizeof(pkt)); |
||||
bd->subcmd = MEDIA_SUBCMD_BLOCK_DONE; |
||||
memcpy(bd->media_id, dl.media_id, 16); |
||||
memcpy(bd->block_id, dl.block_ids, 16); |
||||
bd->chunk = 0; bd->total_size = CHUNK_SIZE; |
||||
memset(bd->block_sig, 0xAA, 64); /* WRONG */ |
||||
media_download_handle_done(inst, pkt, sizeof(pkt)); |
||||
|
||||
/* file should NOT be assembled (sig mismatch) */ |
||||
if (!file_exists(dl.dest_path)) OK(); else FAIL("assembled despite bad sig"); |
||||
|
||||
done4: |
||||
u_free(dl.block_ids); u_free(dl.block_sigs); |
||||
if (inst->md.downloads) queue_free(inst->md.downloads); |
||||
u_free(inst); sqlite3_close(db); |
||||
} |
||||
} |
||||
|
||||
static void test_download_cancel(void) { |
||||
TEST("download cancel marks inactive and calls done_cb"); { |
||||
sqlite3* db = NULL; sqlite3_open(":memory:", &db); |
||||
struct UTUN_INSTANCE* inst = make_minimal_instance(db); |
||||
inst->md.downloads = queue_new(inst->ua, 256, offsetof(struct media_download, media_id), 16, "md_dl_test5"); |
||||
|
||||
struct media_download dl; memset(&dl, 0, sizeof(dl)); |
||||
make_uuid(dl.media_id); |
||||
dl.num_blocks = 1; |
||||
snprintf(dl.dest_path, sizeof(dl.dest_path), "%s/cancel.bin", g_temp_dir); |
||||
dl.block_ids = u_malloc(16); make_uuid(dl.block_ids); |
||||
dl.block_sigs = u_malloc(64); memset(dl.block_sigs, 0, 64); |
||||
dl.active = 1; |
||||
memcpy(dl.ll.data, dl.media_id, 16); |
||||
|
||||
struct ll_entry* qe = queue_entry_new(sizeof(struct media_download)); |
||||
if (!qe) { FAIL("queue_entry_new"); goto done5; } |
||||
memcpy(qe->data, &dl, sizeof(dl)); queue_data_put_with_index(inst->md.downloads, qe); |
||||
|
||||
int rc = media_download_cancel(inst, dl.media_id, NULL, 0); |
||||
if (rc == 0) OK(); else FAIL("cancel returned %d", rc); |
||||
|
||||
done5: |
||||
u_free(dl.block_ids); u_free(dl.block_sigs); |
||||
if (inst->md.downloads) queue_free(inst->md.downloads); |
||||
u_free(inst); sqlite3_close(db); |
||||
} |
||||
} |
||||
|
||||
int main(void) { |
||||
test_setup(); |
||||
printf("=== test_media_delivery_download ===\n"); |
||||
|
||||
test_download_chunk(); |
||||
test_download_done(); |
||||
test_download_cancel(); |
||||
|
||||
printf("\n%d/%d passed, %d failed\n", g_passed, g_total, g_failed); |
||||
test_cleanup(); |
||||
return g_failed > 0 ? 1 : 0; |
||||
} |
||||
@ -0,0 +1,362 @@
|
||||
// test_media_delivery_sql.c — SQL-таблицы + протокольные структуры media_delivery
|
||||
//
|
||||
// Покрытие:
|
||||
// 1. Создание таблиц block_availability + super_sync + индексов
|
||||
// 2. INSERT OR REPLACE (upsert) — дубликат не создаёт вторую строку
|
||||
// 3. SELECT с фильтром по block_uuid, сортировка timestamp DESC
|
||||
// 4. SELECT с id > N, сортировка ASC, LIMIT
|
||||
// 5. super_sync: INSERT OR REPLACE + SELECT (дефолт 0)
|
||||
// 6. DELETE WHERE node_id=?
|
||||
// 7. DELETE WHERE node_id!=? (чистка чужих при выключении суперузла)
|
||||
// 8. Размеры packed-структур
|
||||
// 9. Проверка полей packed-структур (нет padding-сюрпризов)
|
||||
// 10. Построение/разбор MEDIA_QUERY
|
||||
// 11. Построение/разбор MEDIA_HAVE_BLOCK
|
||||
// 12. Построение/разбор MEDIA_SUPER_HELLO
|
||||
|
||||
#include "../src/media_delivery/media_delivery_proto.h" |
||||
#include "../lib/debug_config.h" |
||||
|
||||
#include <sqlite3.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
|
||||
static int g_passed = 0, g_failed = 0, g_total = 0; |
||||
|
||||
#define TEST(name) do { g_total++; printf(" %-50s", name); } while(0) |
||||
#define OK() do { g_passed++; printf("OK\n"); } while(0) |
||||
#define FAIL(fmt, ...) do { g_failed++; printf("FAIL: " fmt "\n", ##__VA_ARGS__); } while(0) |
||||
|
||||
static void make_uuid(uint8_t buf[16]) { |
||||
for (int i = 0; i < 16; i++) buf[i] = (uint8_t)(rand() & 0xFF); |
||||
} |
||||
|
||||
static sqlite3* make_db(void) { |
||||
sqlite3* db = NULL; |
||||
sqlite3_open(":memory:", &db); |
||||
sqlite3_exec(db, "PRAGMA journal_mode=memory", NULL, NULL, NULL); |
||||
return db; |
||||
} |
||||
|
||||
static int create_tables(sqlite3* db) { |
||||
const char* sql_ba = |
||||
"CREATE TABLE IF NOT EXISTS block_availability (" |
||||
" id INTEGER PRIMARY KEY AUTOINCREMENT," |
||||
" block_uuid BLOB NOT NULL," |
||||
" group_id INTEGER NOT NULL," |
||||
" node_id INTEGER NOT NULL," |
||||
" chunk INTEGER NOT NULL," |
||||
" timestamp INTEGER NOT NULL)"; |
||||
int rc = sqlite3_exec(db, sql_ba, NULL, NULL, NULL); |
||||
if (rc != SQLITE_OK) return -1; |
||||
sqlite3_exec(db, "CREATE UNIQUE INDEX IF NOT EXISTS idx_ba_uuid_node ON block_availability(block_uuid, node_id)", NULL, NULL, NULL); |
||||
sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS idx_ba_id ON block_availability(id)", NULL, NULL, NULL); |
||||
sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS idx_ba_node_id ON block_availability(node_id)", NULL, NULL, NULL); |
||||
|
||||
const char* sql_ss = |
||||
"CREATE TABLE IF NOT EXISTS super_sync (" |
||||
" peer_node_id INTEGER PRIMARY KEY," |
||||
" last_recv_id INTEGER NOT NULL DEFAULT 0)"; |
||||
rc = sqlite3_exec(db, sql_ss, NULL, NULL, NULL); |
||||
return (rc == SQLITE_OK) ? 0 : -1; |
||||
} |
||||
|
||||
static int table_exists(sqlite3* db, const char* name) { |
||||
sqlite3_stmt* s = NULL; |
||||
int ok = 0; |
||||
if (sqlite3_prepare_v2(db, "SELECT name FROM sqlite_master WHERE type='table' AND name=?", -1, &s, NULL) == SQLITE_OK) { |
||||
sqlite3_bind_text(s, 1, name, -1, SQLITE_STATIC); |
||||
ok = (sqlite3_step(s) == SQLITE_ROW); |
||||
sqlite3_finalize(s); |
||||
} |
||||
return ok; |
||||
} |
||||
|
||||
static int index_exists(sqlite3* db, const char* name) { |
||||
sqlite3_stmt* s = NULL; |
||||
int ok = 0; |
||||
if (sqlite3_prepare_v2(db, "SELECT name FROM sqlite_master WHERE type='index' AND name=?", -1, &s, NULL) == SQLITE_OK) { |
||||
sqlite3_bind_text(s, 1, name, -1, SQLITE_STATIC); |
||||
ok = (sqlite3_step(s) == SQLITE_ROW); |
||||
sqlite3_finalize(s); |
||||
} |
||||
return ok; |
||||
} |
||||
|
||||
static int count_rows(sqlite3* db, const char* table) { |
||||
char sql[128]; snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s", table); |
||||
sqlite3_stmt* s = NULL; |
||||
int n = -1; |
||||
if (sqlite3_prepare_v2(db, sql, -1, &s, NULL) == SQLITE_OK) { |
||||
if (sqlite3_step(s) == SQLITE_ROW) n = sqlite3_column_int(s, 0); |
||||
sqlite3_finalize(s); |
||||
} |
||||
return n; |
||||
} |
||||
|
||||
/* ── test cases ── */ |
||||
|
||||
static void test_tables(void) { |
||||
TEST("create tables exist"); { |
||||
sqlite3* db = make_db(); |
||||
if (create_tables(db) != 0) { FAIL("create_tables failed"); sqlite3_close(db); return; } |
||||
int ba = table_exists(db, "block_availability"); |
||||
int ss = table_exists(db, "super_sync"); |
||||
if (ba && ss) OK(); else FAIL("ba=%d ss=%d", ba, ss); |
||||
sqlite3_close(db); |
||||
} |
||||
|
||||
TEST("create tables idempotent"); { |
||||
sqlite3* db = make_db(); |
||||
if (create_tables(db) != 0) { FAIL("first create"); sqlite3_close(db); return; } |
||||
if (create_tables(db) != 0) { FAIL("second create"); sqlite3_close(db); return; } |
||||
OK(); |
||||
sqlite3_close(db); |
||||
} |
||||
|
||||
TEST("indices exist"); { |
||||
sqlite3* db = make_db(); |
||||
create_tables(db); |
||||
int i1 = index_exists(db, "idx_ba_uuid_node"); |
||||
int i2 = index_exists(db, "idx_ba_id"); |
||||
int i3 = index_exists(db, "idx_ba_node_id"); |
||||
if (i1 && i2 && i3) OK(); else FAIL("i1=%d i2=%d i3=%d", i1, i2, i3); |
||||
sqlite3_close(db); |
||||
} |
||||
} |
||||
|
||||
static void test_ba_insert(void) { |
||||
TEST("ba_insert basic"); { |
||||
sqlite3* db = make_db(); create_tables(db); |
||||
uint8_t uuid[16]; make_uuid(uuid); |
||||
uint64_t gid = 0x100, nid = 0x200; |
||||
const char* sql = "INSERT INTO block_availability(block_uuid,group_id,node_id,chunk,timestamp) VALUES(?,?,?,?,?)"; |
||||
for (int i = 0; i < 3; i++) { |
||||
sqlite3_stmt* s = NULL; |
||||
sqlite3_prepare_v2(db, sql, -1, &s, NULL); |
||||
sqlite3_bind_blob(s, 1, uuid, 16, SQLITE_STATIC); |
||||
sqlite3_bind_int64(s, 2, (sqlite3_int64)gid); |
||||
sqlite3_bind_int64(s, 3, (sqlite3_int64)(nid + (uint64_t)i)); |
||||
sqlite3_bind_int(s, 4, i); |
||||
sqlite3_bind_int64(s, 5, (sqlite3_int64)time(NULL)); |
||||
sqlite3_step(s); sqlite3_finalize(s); |
||||
} |
||||
int n = count_rows(db, "block_availability"); |
||||
if (n == 3) OK(); else FAIL("expected 3 rows got %d", n); |
||||
sqlite3_close(db); |
||||
} |
||||
|
||||
TEST("ba_insert upsert (unique uuid+node)"); { |
||||
sqlite3* db = make_db(); create_tables(db); |
||||
uint8_t uuid[16]; make_uuid(uuid); |
||||
const char* sql = "INSERT OR REPLACE INTO block_availability(block_uuid,group_id,node_id,chunk,timestamp) VALUES(?,?,?,?,?)"; |
||||
for (int pass = 0; pass < 2; pass++) { |
||||
sqlite3_stmt* s = NULL; |
||||
sqlite3_prepare_v2(db, sql, -1, &s, NULL); |
||||
sqlite3_bind_blob(s, 1, uuid, 16, SQLITE_STATIC); |
||||
sqlite3_bind_int64(s, 2, (sqlite3_int64)1); |
||||
sqlite3_bind_int64(s, 3, (sqlite3_int64)0x300); |
||||
sqlite3_bind_int(s, 4, 0); |
||||
sqlite3_bind_int64(s, 5, (sqlite3_int64)(pass + 100)); |
||||
sqlite3_step(s); sqlite3_finalize(s); |
||||
} |
||||
int n = count_rows(db, "block_availability"); |
||||
if (n == 1) OK(); else FAIL("expected 1 row (upsert) got %d", n); |
||||
sqlite3_close(db); |
||||
} |
||||
} |
||||
|
||||
static void test_ba_find(void) { |
||||
TEST("ba_find nodes by uuid"); { |
||||
sqlite3* db = make_db(); create_tables(db); |
||||
uint8_t uuid[16]; make_uuid(uuid); |
||||
const char* ins = "INSERT INTO block_availability(block_uuid,group_id,node_id,chunk,timestamp) VALUES(?,?,?,?,?)"; |
||||
for (int i = 0; i < 5; i++) { |
||||
sqlite3_stmt* s = NULL; sqlite3_prepare_v2(db, ins, -1, &s, NULL); |
||||
sqlite3_bind_blob(s, 1, uuid, 16, SQLITE_STATIC); |
||||
sqlite3_bind_int64(s, 2, (sqlite3_int64)1); |
||||
sqlite3_bind_int64(s, 3, (sqlite3_int64)(0x10 + i)); |
||||
sqlite3_bind_int(s, 4, i); |
||||
sqlite3_bind_int64(s, 5, (sqlite3_int64)(2000 - i)); |
||||
sqlite3_step(s); sqlite3_finalize(s); |
||||
} |
||||
const char* q = "SELECT node_id FROM block_availability WHERE block_uuid=? ORDER BY timestamp DESC LIMIT 3"; |
||||
sqlite3_stmt* s = NULL; |
||||
sqlite3_prepare_v2(db, q, -1, &s, NULL); |
||||
sqlite3_bind_blob(s, 1, uuid, 16, SQLITE_STATIC); |
||||
int count = 0, first_node = -1; |
||||
while (sqlite3_step(s) == SQLITE_ROW) { if (count == 0) first_node = sqlite3_column_int(s, 0); count++; } |
||||
sqlite3_finalize(s); |
||||
if (count == 3 && first_node == 0x10) OK(); else FAIL("count=%d first_node=0x%x", count, first_node); |
||||
sqlite3_close(db); |
||||
} |
||||
} |
||||
|
||||
static void test_ba_get_since(void) { |
||||
TEST("ba_get_since id > N"); { |
||||
sqlite3* db = make_db(); create_tables(db); |
||||
uint8_t uuid[16]; make_uuid(uuid); |
||||
const char* ins = "INSERT INTO block_availability(block_uuid,group_id,node_id,chunk,timestamp) VALUES(?,?,?,?,?)"; |
||||
for (int i = 0; i < 6; i++) { |
||||
sqlite3_stmt* s = NULL; sqlite3_prepare_v2(db, ins, -1, &s, NULL); |
||||
sqlite3_bind_blob(s, 1, uuid, 16, SQLITE_STATIC); |
||||
sqlite3_bind_int64(s, 2, (sqlite3_int64)1); |
||||
sqlite3_bind_int64(s, 3, (sqlite3_int64)(0x20 + i)); |
||||
sqlite3_bind_int(s, 4, i); |
||||
sqlite3_bind_int64(s, 5, (sqlite3_int64)1000); |
||||
sqlite3_step(s); sqlite3_finalize(s); |
||||
} |
||||
const char* q = "SELECT id FROM block_availability WHERE id > ? ORDER BY id ASC LIMIT 4"; |
||||
sqlite3_stmt* s = NULL; |
||||
sqlite3_prepare_v2(db, q, -1, &s, NULL); |
||||
sqlite3_bind_int64(s, 1, 1); |
||||
int count = 0; |
||||
while (sqlite3_step(s) == SQLITE_ROW) count++; |
||||
sqlite3_finalize(s); |
||||
if (count == 4) OK(); else FAIL("expected 4 rows got %d", count); |
||||
sqlite3_close(db); |
||||
} |
||||
} |
||||
|
||||
static void test_ss_ops(void) { |
||||
TEST("super_sync insert+select"); { |
||||
sqlite3* db = make_db(); create_tables(db); |
||||
sqlite3_exec(db, "INSERT OR REPLACE INTO super_sync(peer_node_id,last_recv_id) VALUES(0xAA, 42)", NULL, NULL, NULL); |
||||
sqlite3_stmt* s = NULL; |
||||
sqlite3_prepare_v2(db, "SELECT last_recv_id FROM super_sync WHERE peer_node_id=?", -1, &s, NULL); |
||||
sqlite3_bind_int64(s, 1, 0xAA); |
||||
int v = -1; |
||||
if (sqlite3_step(s) == SQLITE_ROW) v = sqlite3_column_int(s, 0); |
||||
sqlite3_finalize(s); |
||||
if (v == 42) OK(); else FAIL("expected 42 got %d", v); |
||||
sqlite3_close(db); |
||||
} |
||||
|
||||
TEST("super_sync default 0"); { |
||||
sqlite3* db = make_db(); create_tables(db); |
||||
sqlite3_stmt* s = NULL; |
||||
sqlite3_prepare_v2(db, "SELECT last_recv_id FROM super_sync WHERE peer_node_id=?", -1, &s, NULL); |
||||
sqlite3_bind_int64(s, 1, 0xCC); |
||||
int v = -1; |
||||
if (sqlite3_step(s) == SQLITE_ROW) v = sqlite3_column_int(s, 0); |
||||
else v = 0; /* no row = default */ |
||||
sqlite3_finalize(s); |
||||
if (v == 0) OK(); else FAIL("expected 0 got %d", v); |
||||
sqlite3_close(db); |
||||
} |
||||
} |
||||
|
||||
static void test_ba_delete(void) { |
||||
TEST("ba_delete by node_id"); { |
||||
sqlite3* db = make_db(); create_tables(db); |
||||
uint8_t uuid[16]; make_uuid(uuid); |
||||
const char* ins = "INSERT INTO block_availability(block_uuid,group_id,node_id,chunk,timestamp) VALUES(?,?,?,?,?)"; |
||||
sqlite3_stmt* s = NULL; |
||||
sqlite3_prepare_v2(db, ins, -1, &s, NULL); |
||||
sqlite3_bind_blob(s, 1, uuid, 16, SQLITE_STATIC); sqlite3_bind_int64(s, 2, 1); |
||||
sqlite3_bind_int64(s, 3, 0x500); sqlite3_bind_int(s, 4, 0); |
||||
sqlite3_bind_int64(s, 5, 100); sqlite3_step(s); sqlite3_finalize(s); |
||||
sqlite3_prepare_v2(db, ins, -1, &s, NULL); |
||||
sqlite3_bind_blob(s, 1, uuid, 16, SQLITE_STATIC); sqlite3_bind_int64(s, 2, 1); |
||||
sqlite3_bind_int64(s, 3, 0x600); sqlite3_bind_int(s, 4, 0); |
||||
sqlite3_bind_int64(s, 5, 100); sqlite3_step(s); sqlite3_finalize(s); |
||||
if (count_rows(db, "block_availability") != 2) { FAIL("setup failed"); sqlite3_close(db); return; } |
||||
sqlite3_exec(db, "DELETE FROM block_availability WHERE node_id=0x500", NULL, NULL, NULL); |
||||
int n = count_rows(db, "block_availability"); |
||||
if (n == 1) OK(); else FAIL("expected 1 got %d", n); |
||||
sqlite3_close(db); |
||||
} |
||||
|
||||
TEST("ba_delete foreign (node_id != self)"); { |
||||
sqlite3* db = make_db(); create_tables(db); |
||||
uint8_t uuid[16]; make_uuid(uuid); |
||||
const char* ins = "INSERT INTO block_availability(block_uuid,group_id,node_id,chunk,timestamp) VALUES(?,?,?,?,?)"; |
||||
sqlite3_stmt* s = NULL; |
||||
for (int i = 0; i < 3; i++) { |
||||
sqlite3_prepare_v2(db, ins, -1, &s, NULL); |
||||
sqlite3_bind_blob(s, 1, uuid, 16, SQLITE_STATIC); sqlite3_bind_int64(s, 2, 1); |
||||
sqlite3_bind_int64(s, 3, (sqlite3_int64)(0x700 + i)); sqlite3_bind_int(s, 4, i); |
||||
sqlite3_bind_int64(s, 5, 100); sqlite3_step(s); sqlite3_finalize(s); |
||||
} |
||||
char buf[128]; snprintf(buf, sizeof(buf), "DELETE FROM block_availability WHERE node_id!=0x700"); |
||||
sqlite3_exec(db, buf, NULL, NULL, NULL); |
||||
int n = count_rows(db, "block_availability"); |
||||
if (n == 1) OK(); else FAIL("expected 1 (self) got %d", n); |
||||
sqlite3_close(db); |
||||
} |
||||
} |
||||
|
||||
static void test_proto_sizes(void) { |
||||
TEST("packed struct sizes"); { |
||||
int ok = 1; |
||||
if (sizeof(struct media_pkt_query) != 27) { FAIL("query: %zu != 27", sizeof(struct media_pkt_query)); ok = 0; } |
||||
if (sizeof(struct media_pkt_have_block) != 117) { FAIL("have_block: %zu != 117", sizeof(struct media_pkt_have_block)); ok = 0; } |
||||
if (sizeof(struct media_pkt_super_hello) != 9) { FAIL("super_hello: %zu != 9", sizeof(struct media_pkt_super_hello)); ok = 0; } |
||||
if (sizeof(struct media_pkt_super_ack) != 5) { FAIL("super_ack: %zu != 5", sizeof(struct media_pkt_super_ack)); ok = 0; } |
||||
if (sizeof(struct media_pkt_block_req) != 45) { FAIL("block_req: %zu != 45", sizeof(struct media_pkt_block_req)); ok = 0; } |
||||
if (sizeof(struct media_pkt_cancel) != 37) { FAIL("cancel: %zu != 37", sizeof(struct media_pkt_cancel)); ok = 0; } |
||||
if (sizeof(struct media_pkt_block_done) != 105) { FAIL("block_done: %zu != 105", sizeof(struct media_pkt_block_done)); ok = 0; } |
||||
if (sizeof(struct media_pkt_query_resp_entry) != 30) { FAIL("query_resp_entry: %zu != 30", sizeof(struct media_pkt_query_resp_entry)); ok = 0; } |
||||
if (ok) OK(); |
||||
/* continue running even if sizes mismatch */ |
||||
} |
||||
} |
||||
|
||||
static void test_proto_build(void) { |
||||
TEST("build MEDIA_QUERY"); { |
||||
uint8_t mid[16]; memset(mid, 0xAB, 16); |
||||
uint8_t bids[32]; memset(bids, 0xCD, 32); |
||||
uint8_t pkt[256]; |
||||
struct media_pkt_query* q = (struct media_pkt_query*)pkt; |
||||
q->subcmd = MEDIA_SUBCMD_QUERY; |
||||
q->group_id = 0x12345678; |
||||
memcpy(q->media_id, mid, 16); |
||||
q->num_blocks = 2; |
||||
memcpy(pkt + MEDIA_QUERY_HDR_SIZE, bids, 32); |
||||
|
||||
if (q->subcmd == MEDIA_SUBCMD_QUERY && q->group_id == 0x12345678 && |
||||
q->num_blocks == 2 && memcmp(q->media_id, mid, 16) == 0 && |
||||
memcmp(pkt + MEDIA_QUERY_HDR_SIZE, bids, 32) == 0) OK(); |
||||
else FAIL(); |
||||
} |
||||
|
||||
TEST("build MEDIA_HAVE_BLOCK"); { |
||||
struct media_pkt_have_block hb; |
||||
memset(&hb, 0, sizeof(hb)); |
||||
hb.subcmd = MEDIA_SUBCMD_HAVE_BLOCK; |
||||
hb.group_id = 0xAA; |
||||
hb.chunk = 5; |
||||
hb.timestamp = 999; |
||||
if (hb.subcmd == MEDIA_SUBCMD_HAVE_BLOCK && hb.group_id == 0xAA && |
||||
hb.chunk == 5 && hb.timestamp == 999) OK(); |
||||
else FAIL(); |
||||
} |
||||
|
||||
TEST("build MEDIA_SUPER_HELLO"); { |
||||
struct media_pkt_super_hello sh; |
||||
sh.subcmd = MEDIA_SUBCMD_SUPER_HELLO; |
||||
sh.last_recv_id = 12345; |
||||
if (sh.subcmd == MEDIA_SUBCMD_SUPER_HELLO && sh.last_recv_id == 12345) OK(); |
||||
else FAIL(); |
||||
} |
||||
} |
||||
|
||||
int main(void) { |
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); |
||||
srand((unsigned)time(NULL)); |
||||
printf("=== test_media_delivery_sql ===\n"); |
||||
|
||||
test_tables(); |
||||
test_ba_insert(); |
||||
test_ba_find(); |
||||
test_ba_get_since(); |
||||
test_ss_ops(); |
||||
test_ba_delete(); |
||||
test_proto_sizes(); |
||||
test_proto_build(); |
||||
|
||||
printf("\n%d/%d passed, %d failed\n", g_passed, g_total, g_failed); |
||||
return g_failed > 0 ? 1 : 0; |
||||
} |
||||
Loading…
Reference in new issue