From 8230eb4de7ad8d8b23fb6ca7a0ca308d95946de2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 21:16:59 +0200 Subject: [PATCH] Test final-page update races and replication read failures --- tests/test_media_replication.c | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/test_media_replication.c b/tests/test_media_replication.c index 8319f0d1..8baa5138 100644 --- a/tests/test_media_replication.c +++ b/tests/test_media_replication.c @@ -228,6 +228,20 @@ static void test_atomic_and_validation(void) { bad = response; struct media_pkt_sync_entry* rows = (struct media_pkt_sync_entry*)(bad.data + MEDIA_SYNC_RESP_HDR_SIZE); rows[1].status = 3; receive(a, &bad); + for (int fault = 0; fault < 8; fault++) { + bad = response; + struct media_pkt_sync_resp* h = (struct media_pkt_sync_resp*)bad.data; + rows = (struct media_pkt_sync_entry*)(bad.data + sizeof(*h)); + if (fault == 0) bad.len++; + if (fault == 1) write_le(&h->num_entries, 33, 2); + if (fault == 2) h->status = 3; + if (fault == 3) write_le(&h->next_id, UINT64_MAX, 8); + if (fault == 4) rows[1].id = rows[0].id; + if (fault == 5) memset(&rows[0].node_id, 0, 8); + if (fault == 6) write_le(&rows[1].timestamp, UINT64_MAX, 8); + if (fault == 7) memcpy(rows[1].block_id, rows[0].block_id, 16); + receive(a, &bad); + } REQUIRE(sp->request_pending && sp->deadline_tb == deadline && !scalar(a->md.db, "SELECT COUNT(*) FROM super_sync")); REQUIRE(!scalar(a->md.db, "SELECT COUNT(*) FROM block_availability")); const char* failures[] = { @@ -304,6 +318,38 @@ static struct packet respond_to(struct UTUN_INSTANCE* source, const struct packe static int reject_commit(void* arg) { (void)arg; return 1; } +static int deny_rows(void* arg, int action, const char* table, const char* column, const char* db, const char* trigger) { + (void)arg; (void)column; (void)db; (void)trigger; + return action == SQLITE_READ && table && !strcmp(table, "block_availability") ? SQLITE_DENY : SQLITE_OK; +} + +/* Обновление после последнего snapshot: обе перестановки CHANGED/RESP и потеря уведомления. */ +static void test_hints_and_read_error(void) { + for (int order = 0; order < 3; order++) { + struct UTUN_INSTANCE* a = node(SERVER), *b = node(CLIENT); + begin_pull(a, CLIENT, GROUP); + struct packet q = request_from(a, CLIENT); + struct media_super_peer* sp = peer(a, CLIENT, GROUP); + uint64_t deadline = sp->deadline_tb; + REQUIRE(sqlite3_set_authorizer(b->md.db, deny_rows, NULL) == SQLITE_OK); + struct packet error = respond_to(b, &q); + REQUIRE(((struct media_pkt_sync_resp*)error.data)->status == MEDIA_SYNC_DB_ERROR && error.len == MEDIA_SYNC_RESP_HDR_SIZE); + receive(a, &error); REQUIRE(sp->request_pending && sp->deadline_tb == deadline); + REQUIRE(sqlite3_set_authorizer(b->md.db, NULL, NULL) == SQLITE_OK); + struct packet empty = respond_to(b, &q); + struct media_index_result r = result(1); seed(b, &r, 1, 1, 0); + struct packet hint = {.from = CLIENT, .to = SERVER, .group = GROUP, .len = 1, .data = {MEDIA_SUBCMD_CHANGED}}; + if (order == 0) receive(a, &hint); + receive(a, &empty); + if (order == 1) receive(a, &hint); + if (order != 0) { packet_count = 0; expire(sp); } /* order=2: потерянный hint, истечение idle-сверки. */ + q = request_from(a, CLIENT); + struct packet filled = respond_to(b, &q); receive(a, &filled); + REQUIRE(scalar(a->md.db, "SELECT COUNT(*) FROM block_availability") == 1 && !sp->request_pending); + free_node(a); free_node(b); media_index_result_free(&r); reset(); capture_blocks = 1; + } +} + /* Потери запроса/ответа, backoff с тем же UUID, ошибка commit и пересоздание БД источника. */ static void test_retry_and_epoch(void) { struct UTUN_INSTANCE* a = node(SERVER), *b = node(CLIENT); @@ -405,6 +451,7 @@ int main(void) { test_atomic_and_validation(); test_convergence_and_lifecycle(); test_retry_and_epoch(); + test_hints_and_read_error(); test_backpressure(); REQUIRE(cm_opened == cm_closed); uasync_destroy(ua, 0);