diff --git a/tests/Makefile.am b/tests/Makefile.am index 1a165480..0962d716 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -877,6 +877,9 @@ test_media_index_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) check_PROGRAMS += test_media_index_regressions test_media_index_regressions_SOURCES = test_media_index_regressions.c test_media_index_regressions_LDADD = $(LIBUTUN) $(COMMON_LIBS) +if OS_LINUX +test_media_index_regressions_LDFLAGS = -Wl,--wrap=ma_sha256_file +endif test_media_delivery_sql_SOURCES = test_media_delivery_sql.c test_media_delivery_sql_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/media_delivery -I$(top_srcdir)/lib diff --git a/tests/test_media_index_regressions.c b/tests/test_media_index_regressions.c index 58e5b1c3..d8902b14 100644 --- a/tests/test_media_index_regressions.c +++ b/tests/test_media_index_regressions.c @@ -18,6 +18,16 @@ static uint8_t key[32] = {1}, ids[32] = {1}; static struct media_index_result result = { .media_id = {2}, .content_hash = {3}, .file_size = 14, .block_size = 7, .num_blocks = 2, .block_ids = ids }; +#ifdef __linux__ +int __real_ma_sha256_file(const char*, uint8_t[32]); +int __wrap_ma_sha256_file(const char* path, uint8_t hash[32]) { + if (strstr(path, "/hash-failure")) { + memset(hash, 0, 32); DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "injected hash failure after file copy path=%s", path); return -1; + } + return __real_ma_sha256_file(path, hash); +} +#endif + static void sql(sqlite3* db, const char* query) { assert(sqlite3_exec(db, query, NULL, NULL, NULL) == SQLITE_OK); } static int scalar(sqlite3* db, const char* query) { sqlite3_stmt* st = NULL; assert(sqlite3_prepare_v2(db, query, -1, &st, NULL) == SQLITE_OK); @@ -159,12 +169,17 @@ static void long_path(void) { } static int deleted_events, failed_submissions, successful_submissions; +static char delete_from_event[64]; static void event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int length) { - (void)inst; if (type == CHAT_EVT_CHANNEL_DELETED) deleted_events++; if (type == CHAT_EVT_MSG_SUBMITTED) { assert(length == 9); if (data[8]) successful_submissions++; else failed_submissions++; + if (delete_from_event[0]) { + int before = deleted_events; + assert(CC(inst)->media_submissions); chat_core_delete_channel(inst, delete_from_event); + assert(deleted_events == before); delete_from_event[0] = 0; + } } } @@ -201,13 +216,49 @@ static void pending_deletion(void) { assert(ma_file_size(source) == 14 && ma_file_size(destinations[0]) == -1 && ma_file_size(destinations[1]) == -1); chat_core_ensure_channel_ready(inst, channel); uasync_poll(ua, 0); assert(!scalar(db, "SELECT count(*) FROM media_files")); + + /* Обработчик MSG_SUBMITTED синхронный: удаление должно увидеть ещё живую задачу. */ + chat_core_create_channel_auto(inst, "reentrant"); + assert(!sqlite3_prepare_v2(db, "SELECT channel_id FROM channels WHERE name='reentrant'", -1, &st, NULL)); + assert(sqlite3_step(st) == SQLITE_ROW); + snprintf(req.channel_id, sizeof(req.channel_id), "%s", sqlite3_column_text(st, 0)); sqlite3_finalize(st); + snprintf(delete_from_event, sizeof(delete_from_event), "%s", req.channel_id); + req.request_id = 4; chat_core_submit_media_message(inst, &req); + for (int i = 0; i < 2000 && deleted_events < 2; i++) uasync_poll(ua, 100); + assert(deleted_events == 2 && successful_submissions == 1 && failed_submissions == 3 && !CC(inst)->media_submissions); + assert(!scalar(db, "SELECT count(*) FROM channels WHERE name='reentrant'")); + assert(ma_file_size(destinations[1]) == -1 && ma_file_size(source) == 14); utun_instance_destroy(inst); uasync_destroy(ua, 0); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "media regression pending deletion/two completions/recreation PASS"); } +#ifdef __linux__ +static void preparation_failed(void* arg, int error, const struct media_index_result* value) { + int* completed = arg; assert(error && !value); (*completed)++; +} +static void failed_preparation(void) { + char source[MEDIA_PATH_MAX], destination[MEDIA_PATH_MAX]; + assert(!media_index_path(root, "copy-source", source) && !media_index_path(root, "hash-failure", destination)); + write_file(source); sqlite3* db = database(); + struct UASYNC* ua = uasync_create(); assert(ua); + struct media_async* ma = media_async_create(); assert(ma); int completed = 0; + media_index_register_async(ma, ua, db, 1, key, "failed", source, destination, 1, root, preparation_failed, &completed); + for (int i = 0; i < 2000 && !completed; i++) uasync_poll(ua, 100); + assert(completed == 1 && ma_file_size(destination) == 14 && !scalar(db, "SELECT count(*) FROM media_files")); + assert(scalar(db, "SELECT count(*) FROM media_preparations") == 1); + assert(!media_index_delete_channel(db, "failed", root)); + assert(ma_file_size(destination) == -1 && ma_file_size(source) == 14); + media_async_destroy(ma); uasync_destroy(ua, 0); sqlite3_close(db); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "media regression failed preparation cleanup PASS"); +} +#endif + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO); assert(!test_mkdtemp(root)); ids[16] = 2; paths(); transactions(); deletion(); long_path(); pending_deletion(); +#ifdef __linux__ + failed_preparation(); +#endif DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "test_media_index_regressions PASS"); return 0; }