/* test_zarray_reserve.c — job-array-single-wal-record task 7.1 (bug-1443) * * Unit tests for xzarray_reserve() (zarray.c), the new capacity-reservation * primitive kvmgr_reserve() (kvmgr.c/kvmgr.h) uses to pre-grow an * xkvmgr_type instance's backing value store before a bulk-insert pass * (e.g. restore_job_array()'s bulk-insert loop, job.c) -- closing the * DOMINANT, N-scaling failure trigger for that pass (the large realloc() * reachable through vstore -> xzarray_ensure_items). * * xzarray_t is fully opaque outside zarray.c, so these tests use the two * new XCE_TESTING-guarded introspection/fault-injection primitives added * alongside xzarray_reserve() itself: * - xzarray_test_get_caps() -- read current backing capacity * - xzarray_reserve_test_set/clear_force_fail() -- simulate an allocation * failure without needing genuine OOM * * Covers: * - xzarray_reserve() genuinely pre-grows capacity: reserving N+1 items * up front means a subsequent sequence of N real vstore() calls causes * NO further capacity growth at all (proven via xzarray_test_get_caps() * staying constant across every store). * * The "+1" is not incidental -- it is required by xzarray_ensure_items() * (zarray.c, unmodified)'s own strict `mincap < caps` sufficiency check: * reserving EXACTLY N and then performing N stores still hits * `mincap == caps` on the very last store, which that strict check * treats as insufficient and grows again (by a small, delta-bounded * amount). This is verified directly below (see * test_xzarray_reserve_off_by_one_without_extra_headroom) rather than * assumed. * - Without any reservation, the identical N-store sequence DOES grow * capacity partway through (the behavior task 7.1 improves on). * - A simulated allocation failure is correctly propagated as -ENOMEM. * - Defensive/boundary: NULL array, negative n. */ #include #include #include #include #include #include "zarray.h" static var make_test_xzarray(int delta) { xzarray_params_t params = { .delta = delta, .datakiller = NULL }; return makeInstance(&xzarray_type, ¶ms); } /* ── xzarray_reserve() genuinely pre-grows capacity (with the verified +1 * headroom) -- zero further growth during N subsequent real stores ──────── */ static void test_xzarray_reserve_pregrows_capacity_no_growth_during_subsequent_stores(void) { const int delta = 1; /* small delta -- growth would otherwise be * triggered on almost every store, making this the * worst case (and the one bug-1443 cares about: * arrays larger than the natural delta margin). */ const int n = 20; var array = make_test_xzarray(delta); TEST_ASSERT_NOT_NULL(array); /* Reserve n+1 -- see the file doc comment for why +1 is required. */ int reserve_rc = xzarray_reserve(array, n + 1); TEST_ASSERT_TRUE_MESSAGE(reserve_rc >= 0, "xzarray_reserve must succeed for a valid array and count"); int caps_after_reserve = xzarray_test_get_caps(array); TEST_ASSERT_TRUE_MESSAGE(caps_after_reserve >= n + 1, "reservation must genuinely grow capacity to cover n+1 items"); for (int i = 0; i < n; i++) { char label[32]; snprintf(label, sizeof(label), "item-%d", i); int idx = vstore(array, (void*)"x"); TEST_ASSERT_TRUE(idx >= 0); int caps_now = xzarray_test_get_caps(array); TEST_ASSERT_EQUAL_INT_MESSAGE(caps_after_reserve, caps_now, "capacity must NOT change during any of the n reserved stores --" " a change here means a realloc() happened despite a successful" " reservation, defeating the whole point of task 7.1"); } destroy(array); printf("test_xzarray_reserve_pregrows_capacity_no_growth_during_" "subsequent_stores: PASS\n"); } /* ── The verified off-by-one: reserving EXACTLY n and then storing n items * still triggers exactly one (small, delta-bounded) growth on the LAST * store -- documents/proves why job.c's caller reserves array_size + 1, not * array_size, to fully close growth during its bulk-insert pass. ────────── */ static void test_xzarray_reserve_off_by_one_without_extra_headroom(void) { const int delta = 1; const int n = 10; var array = make_test_xzarray(delta); TEST_ASSERT_NOT_NULL(array); int reserve_rc = xzarray_reserve(array, n); /* exactly n, no +1 */ TEST_ASSERT_TRUE(reserve_rc >= 0); int caps_after_reserve = xzarray_test_get_caps(array); int grew_during_stores = 0; for (int i = 0; i < n; i++) { vstore(array, (void*)"x"); if (xzarray_test_get_caps(array) != caps_after_reserve) { grew_during_stores = 1; } } TEST_ASSERT_TRUE_MESSAGE(grew_during_stores, "reserving EXACTLY n and then storing n items is expected to still" " trigger one growth cycle on the last store (xzarray_ensure_items's" " strict mincap < caps check) -- this is the documented reason" " job.c's kvmgr_reserve() call reserves array_size + 1, not" " array_size"); destroy(array); printf("test_xzarray_reserve_off_by_one_without_extra_headroom: PASS\n"); } /* ── Baseline contrast: WITHOUT any reservation, the same N-store sequence * genuinely does grow capacity (the pre-task-7.1 behavior) ─────────────── */ static void test_xzarray_without_reservation_growth_occurs_during_stores(void) { const int delta = 1; const int n = 20; var array = make_test_xzarray(delta); TEST_ASSERT_NOT_NULL(array); int caps_initial = xzarray_test_get_caps(array); TEST_ASSERT_EQUAL_INT(0, caps_initial); int grew_during_stores = 0; for (int i = 0; i < n; i++) { int before = xzarray_test_get_caps(array); vstore(array, (void*)"x"); int after = xzarray_test_get_caps(array); if (after != before) { grew_during_stores = 1; } } TEST_ASSERT_TRUE_MESSAGE(grew_during_stores, "without a reservation, growth during the store sequence is" " expected -- this is the pre-existing baseline task 7.1 improves" " on, not a new claim"); destroy(array); printf("test_xzarray_without_reservation_growth_occurs_during_stores: " "PASS\n"); } /* ── Simulated allocation failure propagates as -ENOMEM ─────────────────── */ static void test_xzarray_reserve_propagates_simulated_allocation_failure(void) { var array = make_test_xzarray(4); TEST_ASSERT_NOT_NULL(array); xzarray_reserve_test_set_force_fail(); int rc = xzarray_reserve(array, 10); TEST_ASSERT_EQUAL_INT_MESSAGE(-ENOMEM, rc, "a forced allocation failure must propagate as -ENOMEM"); xzarray_reserve_test_clear_force_fail(); /* Clearing the seam must restore normal behavior. */ int rc2 = xzarray_reserve(array, 10); TEST_ASSERT_TRUE_MESSAGE(rc2 >= 0, "clearing the force-fail seam must restore normal success"); destroy(array); printf("test_xzarray_reserve_propagates_simulated_allocation_failure: " "PASS\n"); } /* ── Defensive / boundary ─────────────────────────────────────────────── */ static void test_xzarray_reserve_rejects_null_array(void) { int rc = xzarray_reserve(NULL, 5); TEST_ASSERT_TRUE(rc < 0); printf("test_xzarray_reserve_rejects_null_array: PASS\n"); } static void test_xzarray_reserve_rejects_negative_n(void) { var array = make_test_xzarray(4); TEST_ASSERT_NOT_NULL(array); int rc = xzarray_reserve(array, -1); TEST_ASSERT_TRUE(rc < 0); destroy(array); printf("test_xzarray_reserve_rejects_negative_n: PASS\n"); } static void test_xzarray_reserve_zero_is_noop_success(void) { var array = make_test_xzarray(4); TEST_ASSERT_NOT_NULL(array); int rc = xzarray_reserve(array, 0); TEST_ASSERT_TRUE(rc >= 0); destroy(array); printf("test_xzarray_reserve_zero_is_noop_success: PASS\n"); } /* ── Test group entry point ───────────────────────────────────────────── */ void test_zarray_reserve(void) { printf("\n>> xzarray_reserve test\n"); RUN_TEST( test_xzarray_reserve_pregrows_capacity_no_growth_during_subsequent_stores); RUN_TEST(test_xzarray_reserve_off_by_one_without_extra_headroom); RUN_TEST(test_xzarray_without_reservation_growth_occurs_during_stores); RUN_TEST(test_xzarray_reserve_propagates_simulated_allocation_failure); RUN_TEST(test_xzarray_reserve_rejects_null_array); RUN_TEST(test_xzarray_reserve_rejects_negative_n); RUN_TEST(test_xzarray_reserve_zero_is_noop_success); }