From a9b181beb7fcd3295bf7b1acfa2a7c1e3b688663 Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Mon, 4 May 2026 17:16:01 +0300 Subject: [PATCH] test(bench): add F17 forward-iteration coverage symmetric to F16 --- tests/bench/bench_failure_modes.c | 110 +++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/tests/bench/bench_failure_modes.c b/tests/bench/bench_failure_modes.c index 1cef9546..79e5a1ad 100644 --- a/tests/bench/bench_failure_modes.c +++ b/tests/bench/bench_failure_modes.c @@ -7,7 +7,7 @@ * bench_failure_modes.c * vim: expandtab:ts=4:sts=4:sw=4 * - * F1–F16 failure-injection tests. Each test crafts a deliberately corrupt + * F1–F17 failure-injection tests. Each test crafts a deliberately corrupt * or pathological flat-file and asserts how the backend handles it: * * F1 Truncated last line (crash mid-fwrite simulation) @@ -22,6 +22,7 @@ * F14 mtime/inode flip — file replaced under us, ensure_fresh must rebuild * F15 Empty file (0 bytes) — verify reports INFO and continues * F16 Page-Up cursor must not stick on entries[0] (regression guard) + * F17 Forward iteration via start_time covers every message (no gaps) * * Each test prints PASS/FAIL with detail and writes a CSV row: * F#, "failure", file_size, line_count, wall_ms, peak_rss_kb, note @@ -692,6 +693,112 @@ test_F16(fail_ctx_t* ctx) be->close(); } +// --------------------------------------------------------------------------- +// F17 — Forward iteration (start_time + from_start=TRUE) reaches every +// message without gaps. +// +// Symmetric counterpart to F16: instead of paging from newest backwards +// via end_time, page from oldest forwards via start_time. Each iteration +// passes start_time = timestamp of the newest message returned in the +// previous batch; the backend should return the next batch of strictly +// newer messages until the file is exhausted. +// +// This exercises a different branch of _flatfile_get_previous_chat: +// - read_from = ff_state_offset_for_time(start_time) (not bom_len) +// - read_to = EOF (no end_time bisect) +// - filter drops msgs with timestamp <= start_dt +// - pagination keeps FIRST MESSAGES_TO_RETRIEVE (not last) +// The path bypasses cursor entirely (start_time triggers cursor=-1 reset). +// +// Pass criterion: total messages received == total written. + +static void +test_F17(fail_ctx_t* ctx) +{ + if (!test_enabled(ctx->tests, "F17")) return; + auto_gchar gchar* path = setup_test_dir(ctx->tmp_dir, "F17"); + + const int total_msgs = 2000; + FILE* fp = fopen(path, "w"); + fprintf(fp, "%s", FLATFILE_HEADER); + for (int i = 0; i < total_msgs; i++) + write_normal_line(fp, i, "forward-iter regression payload"); + fclose(fp); + + g_free(g_flatfile_account_jid); + g_flatfile_account_jid = g_strdup(ACCOUNT_JID); + setenv("BENCH_ACCOUNT_JID", ACCOUNT_JID, 1); + + db_backend_t* be = db_backend_flatfile(); + if (!be || !be->init || !be->get_previous_chat || !be->close) { + report(ctx, "F17", FALSE, 0, path, "flatfile backend not available"); + return; + } + ProfAccount fake = { 0 }; + fake.jid = (gchar*)ACCOUNT_JID; + if (!be->init(&fake)) { + report(ctx, "F17", FALSE, 0, path, "flatfile init failed"); + return; + } + + int total_received = 0; + int empty_responses = 0; + int iterations = 0; + // Initial start_time before the first written message so the first + // call returns msgs starting at index 0. + auto_gchar gchar* start_time = g_strdup("2025-06-15T11:00:00Z"); + + double t0 = bench_now_ms(); + + while (iterations < 200) { + iterations++; + GSList* batch = NULL; + db_history_result_t r = be->get_previous_chat( + CONTACT_JID, start_time, NULL, TRUE, FALSE, &batch); + + if (r == DB_RESPONSE_EMPTY) { + empty_responses++; + break; + } + if (r != DB_RESPONSE_SUCCESS || !batch) { + empty_responses++; + break; + } + + // pre_result is sorted oldest-first; with from_start=TRUE we keep + // the first MESSAGES_TO_RETRIEVE. The newest in the returned batch + // is therefore the LAST element of the list. + ProfMessage* newest = NULL; + int batch_n = 0; + for (GSList* l = batch; l; l = l->next) { + batch_n++; + newest = l->data; + } + if (!newest || !newest->timestamp) { + g_slist_free_full(batch, (GDestroyNotify)message_free); + break; + } + total_received += batch_n; + + g_free(start_time); + start_time = g_date_time_format_iso8601(newest->timestamp); + + g_slist_free_full(batch, (GDestroyNotify)message_free); + } + + double dt = bench_now_ms() - t0; + + gboolean ok = (total_received == total_msgs) + && (empty_responses == 1) + && (iterations < 200); + + auto_gchar gchar* note = g_strdup_printf( + "wrote=%d received=%d iters=%d empty=%d (forward-iter regression)", + total_msgs, total_received, iterations, empty_responses); + report(ctx, "F17", ok, dt, path, note); + be->close(); +} + // --------------------------------------------------------------------------- // Driver @@ -732,6 +839,7 @@ main(int argc, char** argv) test_F14(&ctx); test_F15(&ctx); test_F16(&ctx); + test_F17(&ctx); fprintf(stderr, "\nfailure-modes summary: %d passed, %d failed\n", ctx.passed, ctx.failed);