LCOV - code coverage report
Current view: top level - libs/mem/tests/src - test_libmem_0009.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 422 422
Test Date: 2026-07-12 01:01:34 Functions: 100.0 % 21 21
Branches: - 0 0

             Branch data     Line data    Source code
       1                 :             : #include "test_libmem_all.h"
       2                 :             : #include "testmocking.h"
       3                 :             : 
       4                 :             : /* Shared descriptors of the telemetry suite.
       5                 :             :    The descriptors live for the whole suite so each subtest observes the
       6                 :             :    state left by the previous scenario and can keep advancing the same
       7                 :             :    buffer through every supported transition */
       8                 :             : static memory shared_buffer = m_init_static(unsigned char);
       9                 :             : static memory shared_sentinel = m_init_static(uint32_t);
      10                 :             : static memory *buffer = &shared_buffer;
      11                 :             : static memory *sentinel = &shared_sentinel;
      12                 :             : 
      13                 :             : /* Suite-wide baseline of the global Telemetry struct, captured by the
      14                 :             :    first subtest. Counters are global, and the testitall framework can
      15                 :             :    also move them while clearing its STDOUT, STDERR, and EXTEND
      16                 :             :    descriptors between subtests, so the suite asserts deltas relative
      17                 :             :    to this baseline rather than absolute counter values */
      18                 :             : static Telemetry suite_baseline;
      19                 :             : 
      20                 :             : /**
      21                 :             :  * @brief Reinitialize the shared descriptors and snapshot the suite baseline
      22                 :             :  *
      23                 :             :  * Resets the shared buffer and sentinel descriptors to an empty state
      24                 :             :  * through m_init so the shared descriptors enter the suite in a known
      25                 :             :  * empty state. Captures the current Telemetry struct as suite_baseline
      26                 :             :  * so every subsequent subtest can express its expectations as deltas
      27                 :             :  * relative to a known starting point. The function deliberately does
      28                 :             :  * not reset global telemetry because previous tests in the runner may
      29                 :             :  * still own descriptors registered in current_active_descriptors and a
      30                 :             :  * hard counter reset would underflow the counter on their later teardown.
      31                 :             :  * The descriptor reset is plain assignment, not m_del, so it does not
      32                 :             :  * recover any blocks left allocated by an aborted previous run of this
      33                 :             :  * same suite
      34                 :             :  *
      35                 :             :  * @return Return describing success or failure
      36                 :             :  */
      37                 :           1 : static Return test_libmem_0009_01(void)
      38                 :             : {
      39                 :           1 :         INITTEST;
      40                 :             : 
      41                 :           1 :         shared_buffer = m_init(unsigned char);
      42                 :           1 :         shared_sentinel = m_init(uint32_t);
      43                 :             : 
      44                 :           1 :         suite_baseline = telemetry;
      45                 :             : 
      46                 :           1 :         ASSERT(buffer->data == NULL);
      47                 :           1 :         ASSERT(buffer->length == 0);
      48                 :           1 :         ASSERT(buffer->string_length == 0);
      49                 :           1 :         ASSERT(buffer->is_string == false);
      50                 :           1 :         ASSERT(buffer->actually_allocated_bytes == 0);
      51                 :             : 
      52                 :           1 :         ASSERT(sentinel->data == NULL);
      53                 :           1 :         ASSERT(sentinel->length == 0);
      54                 :           1 :         ASSERT(sentinel->string_length == 0);
      55                 :           1 :         ASSERT(sentinel->is_string == false);
      56                 :           1 :         ASSERT(sentinel->actually_allocated_bytes == 0);
      57                 :             : 
      58                 :           1 :         RETURN_STATUS;
      59                 :             : }
      60                 :             : 
      61                 :             : /**
      62                 :             :  * @brief Cover the first physical allocation of a descriptor
      63                 :             :  *
      64                 :             :  * Resizes the empty buffer to one element. The library must round the
      65                 :             :  * request up to one slab block, register one fresh allocation, push
      66                 :             :  * the payload counters by one byte, and create one extra active
      67                 :             :  * descriptor relative to the entry baseline of this subtest
      68                 :             :  *
      69                 :             :  * @return Return describing success or failure
      70                 :             :  */
      71                 :           1 : static Return test_libmem_0009_02(void)
      72                 :             : {
      73                 :           1 :         INITTEST;
      74                 :             : 
      75                 :           1 :         const size_t block = MEMORY_BLOCK_BYTES;
      76                 :             : 
      77                 :           1 :         const size_t baseline_current_heap = telemetry.current_heap_reserved_bytes;
      78                 :           1 :         const size_t baseline_current_payload = telemetry.current_payload_bytes;
      79                 :           1 :         const size_t baseline_current_block_overhead = telemetry.current_block_overhead_bytes;
      80                 :           1 :         const size_t baseline_current_active = telemetry.current_active_descriptors;
      81                 :           1 :         const size_t baseline_total_acquired = telemetry.total_heap_reserved_bytes_acquired;
      82                 :           1 :         const size_t baseline_total_payload_added = telemetry.total_payload_bytes_added;
      83                 :           1 :         const size_t baseline_total_block_overhead_added = telemetry.total_block_overhead_bytes_added;
      84                 :           1 :         const size_t baseline_fresh = telemetry.fresh_heap_allocations;
      85                 :           1 :         const size_t baseline_reallocations = telemetry.heap_reallocations;
      86                 :           1 :         const size_t baseline_in_place = telemetry.in_place_resizes;
      87                 :           1 :         const size_t baseline_noop = telemetry.noop_resizes;
      88                 :             : 
      89                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,1));
      90                 :             : 
      91                 :           1 :         ASSERT(telemetry.current_heap_reserved_bytes == baseline_current_heap + block);
      92                 :           1 :         ASSERT(telemetry.current_payload_bytes == baseline_current_payload + 1);
      93                 :           1 :         ASSERT(telemetry.current_block_overhead_bytes == baseline_current_block_overhead + (block - 1));
      94                 :           1 :         ASSERT(telemetry.current_active_descriptors == baseline_current_active + 1);
      95                 :             : 
      96                 :           1 :         ASSERT(telemetry.total_heap_reserved_bytes_acquired == baseline_total_acquired + block);
      97                 :           1 :         ASSERT(telemetry.total_payload_bytes_added == baseline_total_payload_added + 1);
      98                 :           1 :         ASSERT(telemetry.total_block_overhead_bytes_added == baseline_total_block_overhead_added + (block - 1));
      99                 :             : 
     100                 :           1 :         ASSERT(telemetry.fresh_heap_allocations == baseline_fresh + 1);
     101                 :           1 :         ASSERT(telemetry.heap_reallocations == baseline_reallocations);
     102                 :           1 :         ASSERT(telemetry.in_place_resizes == baseline_in_place);
     103                 :           1 :         ASSERT(telemetry.noop_resizes == baseline_noop);
     104                 :             : 
     105                 :           1 :         ASSERT(telemetry.peak_heap_reserved_bytes >= telemetry.current_heap_reserved_bytes);
     106                 :           1 :         ASSERT(telemetry.peak_block_overhead_bytes >= telemetry.current_block_overhead_bytes);
     107                 :           1 :         ASSERT(telemetry.peak_active_descriptors >= telemetry.current_active_descriptors);
     108                 :             : 
     109                 :           1 :         RETURN_STATUS;
     110                 :             : }
     111                 :             : 
     112                 :             : /**
     113                 :             :  * @brief Cover growth that crosses the slab boundary
     114                 :             :  *
     115                 :             :  * Grows the buffer past one slab block so the library must hand the
     116                 :             :  * request to the OS allocator as a real reallocation. heap_reallocations
     117                 :             :  * advances by one, current and cumulative heap reserve grow by exactly
     118                 :             :  * one slab block, and the payload counters move by exactly the added
     119                 :             :  * element count. peak_heap_reserved_bytes is lifted to the new
     120                 :             :  * post-resize value when that value exceeds the existing peak. Block
     121                 :             :  * overhead stays at its entry baseline because the new logical payload
     122                 :             :  * (block + 1) lands inside the new two-block reserve with the same
     123                 :             :  * trailing overhead (block - 1) as before, so current_block_overhead_bytes,
     124                 :             :  * total_block_overhead_bytes_added, and peak_block_overhead_bytes all
     125                 :             :  * stay byte-for-byte equal to their pre-resize values. The full
     126                 :             :  * Telemetry struct is compared against the expected post-state through
     127                 :             :  * memcmp so any unexpected counter movement fails the test
     128                 :             :  *
     129                 :             :  * @return Return describing success or failure
     130                 :             :  */
     131                 :           1 : static Return test_libmem_0009_03(void)
     132                 :             : {
     133                 :           1 :         INITTEST;
     134                 :             : 
     135                 :           1 :         const size_t block = MEMORY_BLOCK_BYTES;
     136                 :           1 :         const size_t big_count = block + 1;
     137                 :             : 
     138                 :           1 :         const Telemetry before_grow = telemetry;
     139                 :             : 
     140                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,big_count));
     141                 :             : 
     142                 :             :         /* The expected post-state is computed from the pre-state by applying
     143                 :             :            exactly the deltas the contract permits for a slab-crossing
     144                 :             :            reallocation: one slab block of fresh heap reserve, the matching
     145                 :             :            acquired-byte total, the payload growth from 1 to big_count
     146                 :             :            elements, and one heap reallocation event. Block-overhead counters
     147                 :             :            stay untouched because the overhead before and after the grow is
     148                 :             :            the same block - 1 bytes. The global peak counter is checked
     149                 :             :            separately because it is monotonic across the whole process */
     150                 :           1 :         Telemetry expected_after_grow = before_grow;
     151                 :           1 :         expected_after_grow.current_heap_reserved_bytes += block;
     152                 :           1 :         expected_after_grow.total_heap_reserved_bytes_acquired += block;
     153                 :           1 :         expected_after_grow.current_payload_bytes += (big_count - 1);
     154                 :           1 :         expected_after_grow.total_payload_bytes_added += (big_count - 1);
     155                 :           1 :         expected_after_grow.heap_reallocations += 1;
     156                 :             : 
     157                 :             :         /* peak_heap_reserved_bytes is global and monotonic, so compare the deterministic counters separately */
     158                 :           1 :         Telemetry observed_after_grow = telemetry;
     159                 :           1 :         observed_after_grow.peak_heap_reserved_bytes = 0;
     160                 :           1 :         expected_after_grow.peak_heap_reserved_bytes = 0;
     161                 :             : 
     162                 :           1 :         ASSERT(memcmp(&observed_after_grow,&expected_after_grow,sizeof(Telemetry)) == 0);
     163                 :           1 :         ASSERT(telemetry.peak_heap_reserved_bytes >= before_grow.peak_heap_reserved_bytes);
     164                 :           1 :         ASSERT(telemetry.peak_heap_reserved_bytes >= telemetry.current_heap_reserved_bytes);
     165                 :             : 
     166                 :           1 :         RETURN_STATUS;
     167                 :             : }
     168                 :             : 
     169                 :             : /**
     170                 :             :  * @brief Cover RELEASE_UNUSED shrink that returns one slab block to the OS
     171                 :             :  *
     172                 :             :  * Shrinks the buffer back to one element with RELEASE_UNUSED set. The
     173                 :             :  * library must release exactly one slab block back to the allocator,
     174                 :             :  * bump release_unused_shrinks and the matching released-bytes totals
     175                 :             :  * by one block, count this transition as one heap reallocation, and
     176                 :             :  * keep the descriptor alive (data still non-NULL, length now 1)
     177                 :             :  *
     178                 :             :  * @return Return describing success or failure
     179                 :             :  */
     180                 :           1 : static Return test_libmem_0009_04(void)
     181                 :             : {
     182                 :           1 :         INITTEST;
     183                 :             : 
     184                 :           1 :         const size_t block = MEMORY_BLOCK_BYTES;
     185                 :             : 
     186                 :           1 :         const size_t baseline_current_heap = telemetry.current_heap_reserved_bytes;
     187                 :           1 :         const size_t baseline_current_payload = telemetry.current_payload_bytes;
     188                 :           1 :         const size_t baseline_total_released = telemetry.total_heap_reserved_bytes_released;
     189                 :           1 :         const size_t baseline_release_shrinks = telemetry.release_unused_shrinks;
     190                 :           1 :         const size_t baseline_total_release_unused = telemetry.total_release_unused_heap_reserved_bytes_released;
     191                 :           1 :         const size_t baseline_reallocations = telemetry.heap_reallocations;
     192                 :             : 
     193                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,1,RELEASE_UNUSED));
     194                 :             : 
     195                 :           1 :         ASSERT(telemetry.current_heap_reserved_bytes == baseline_current_heap - block);
     196                 :           1 :         ASSERT(telemetry.current_payload_bytes == baseline_current_payload - (MEMORY_BLOCK_BYTES + 1 - 1));
     197                 :           1 :         ASSERT(telemetry.total_heap_reserved_bytes_released == baseline_total_released + block);
     198                 :           1 :         ASSERT(telemetry.release_unused_shrinks == baseline_release_shrinks + 1);
     199                 :           1 :         ASSERT(telemetry.total_release_unused_heap_reserved_bytes_released == baseline_total_release_unused + block);
     200                 :           1 :         ASSERT(telemetry.heap_reallocations == baseline_reallocations + 1);
     201                 :             : 
     202                 :           1 :         ASSERT(buffer->length == 1);
     203                 :           1 :         ASSERT(buffer->data != NULL);
     204                 :             : 
     205                 :           1 :         RETURN_STATUS;
     206                 :             : }
     207                 :             : 
     208                 :             : /**
     209                 :             :  * @brief Cover an in-place grow inside the retained slab block
     210                 :             :  *
     211                 :             :  * Grows the descriptor from one to two elements. The slab block
     212                 :             :  * reserved after the previous shrink already covers two bytes, so
     213                 :             :  * the resize must be served as a pure bookkeeping change.
     214                 :             :  * heap_reallocations stays at the value left by the previous shrink,
     215                 :             :  * while in_place_resizes advances by one and the payload counters
     216                 :             :  * move by exactly the new element
     217                 :             :  *
     218                 :             :  * @return Return describing success or failure
     219                 :             :  */
     220                 :           1 : static Return test_libmem_0009_05(void)
     221                 :             : {
     222                 :           1 :         INITTEST;
     223                 :             : 
     224                 :           1 :         const size_t baseline_current_heap = telemetry.current_heap_reserved_bytes;
     225                 :           1 :         const size_t baseline_current_payload = telemetry.current_payload_bytes;
     226                 :           1 :         const size_t baseline_total_payload_added = telemetry.total_payload_bytes_added;
     227                 :           1 :         const size_t baseline_in_place = telemetry.in_place_resizes;
     228                 :           1 :         const size_t baseline_reallocations = telemetry.heap_reallocations;
     229                 :             : 
     230                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,2));
     231                 :             : 
     232                 :           1 :         ASSERT(telemetry.current_heap_reserved_bytes == baseline_current_heap);
     233                 :           1 :         ASSERT(telemetry.current_payload_bytes == baseline_current_payload + 1);
     234                 :           1 :         ASSERT(telemetry.total_payload_bytes_added == baseline_total_payload_added + 1);
     235                 :           1 :         ASSERT(telemetry.in_place_resizes == baseline_in_place + 1);
     236                 :           1 :         ASSERT(telemetry.heap_reallocations == baseline_reallocations);
     237                 :             : 
     238                 :           1 :         ASSERT(buffer->length == 2);
     239                 :           1 :         ASSERT(buffer->data != NULL);
     240                 :             : 
     241                 :           1 :         RETURN_STATUS;
     242                 :             : }
     243                 :             : 
     244                 :             : /**
     245                 :             :  * @brief Cover a sequence of consecutive no-op resizes
     246                 :             :  *
     247                 :             :  * Issues three m_resize calls that ask for the size the descriptor
     248                 :             :  * already has. Each call must skip every allocation path, increment
     249                 :             :  * noop_resizes by one, and advance current_consecutive_noop_resizes by
     250                 :             :  * one. peak_consecutive_noop_resizes must be at least three by the end of
     251                 :             :  * this subtest. None of the allocation, payload, or block-overhead
     252                 :             :  * totals are allowed to change here
     253                 :             :  *
     254                 :             :  * @return Return describing success or failure
     255                 :             :  */
     256                 :           1 : static Return test_libmem_0009_06(void)
     257                 :             : {
     258                 :           1 :         INITTEST;
     259                 :             : 
     260                 :           1 :         const Telemetry before_consecutive_noops = telemetry;
     261                 :             : 
     262                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,2));
     263                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,2));
     264                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,2));
     265                 :             : 
     266                 :             :         /* The expected post-state is computed from the pre-state by applying
     267                 :             :            exactly the deltas a no-op resize is contractually allowed to
     268                 :             :            produce: noop_resizes and current_consecutive_noop_resizes each grow by
     269                 :             :            one per call. The monotonic peak is checked separately because its
     270                 :             :            expected value is max(previous peak, current consecutive count), and the test
     271                 :             :            should not need its own branch to model that rule. Every other
     272                 :             :            counter must be byte-for-byte identical to the pre-state */
     273                 :           1 :         const size_t previous_peak_consecutive_noops = before_consecutive_noops.peak_consecutive_noop_resizes;
     274                 :           1 :         const size_t expected_current_consecutive_noops = before_consecutive_noops.current_consecutive_noop_resizes + 3;
     275                 :           1 :         Telemetry expected_after_consecutive_noops = before_consecutive_noops;
     276                 :           1 :         expected_after_consecutive_noops.noop_resizes += 3;
     277                 :           1 :         expected_after_consecutive_noops.current_consecutive_noop_resizes = expected_current_consecutive_noops;
     278                 :             : 
     279                 :           1 :         Telemetry observed_after_consecutive_noops = telemetry;
     280                 :           1 :         observed_after_consecutive_noops.peak_consecutive_noop_resizes = 0;
     281                 :           1 :         expected_after_consecutive_noops.peak_consecutive_noop_resizes = 0;
     282                 :             : 
     283                 :           1 :         ASSERT(memcmp(&observed_after_consecutive_noops,&expected_after_consecutive_noops,sizeof(Telemetry)) == 0);
     284                 :           1 :         ASSERT(telemetry.peak_consecutive_noop_resizes >= previous_peak_consecutive_noops);
     285                 :           1 :         ASSERT(telemetry.peak_consecutive_noop_resizes >= expected_current_consecutive_noops);
     286                 :           1 :         ASSERT(telemetry.peak_consecutive_noop_resizes == previous_peak_consecutive_noops
     287                 :             :                 || telemetry.peak_consecutive_noop_resizes == expected_current_consecutive_noops);
     288                 :             : 
     289                 :           1 :         RETURN_STATUS;
     290                 :             : }
     291                 :             : 
     292                 :             : /**
     293                 :             :  * @brief Cover the consecutive no-op reset triggered by an effective resize
     294                 :             :  *
     295                 :             :  * Performs an in-place grow that actually changes the logical length.
     296                 :             :  * The library must reset current_consecutive_noop_resizes to zero while
     297                 :             :  * preserving peak_consecutive_noop_resizes from the previous subtest.
     298                 :             :  * in_place_resizes advances by one and the payload counters move by
     299                 :             :  * exactly the new element
     300                 :             :  *
     301                 :             :  * @return Return describing success or failure
     302                 :             :  */
     303                 :           1 : static Return test_libmem_0009_07(void)
     304                 :             : {
     305                 :           1 :         INITTEST;
     306                 :             : 
     307                 :           1 :         const size_t baseline_peak_consecutive_noops = telemetry.peak_consecutive_noop_resizes;
     308                 :           1 :         const size_t baseline_in_place = telemetry.in_place_resizes;
     309                 :           1 :         const size_t baseline_current_payload = telemetry.current_payload_bytes;
     310                 :           1 :         const size_t baseline_total_payload_added = telemetry.total_payload_bytes_added;
     311                 :           1 :         const size_t baseline_noop = telemetry.noop_resizes;
     312                 :             : 
     313                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,3));
     314                 :             : 
     315                 :           1 :         ASSERT(telemetry.current_consecutive_noop_resizes == 0);
     316                 :           1 :         ASSERT(telemetry.peak_consecutive_noop_resizes == baseline_peak_consecutive_noops);
     317                 :           1 :         ASSERT(telemetry.noop_resizes == baseline_noop);
     318                 :           1 :         ASSERT(telemetry.in_place_resizes == baseline_in_place + 1);
     319                 :           1 :         ASSERT(telemetry.current_payload_bytes == baseline_current_payload + 1);
     320                 :           1 :         ASSERT(telemetry.total_payload_bytes_added == baseline_total_payload_added + 1);
     321                 :             : 
     322                 :           1 :         RETURN_STATUS;
     323                 :             : }
     324                 :             : 
     325                 :             : /**
     326                 :             :  * @brief Cover ZERO_NEW_MEMORY zero-fill on growth
     327                 :             :  *
     328                 :             :  * First exposes elements [3..7] without ZERO_NEW_MEMORY, fills them
     329                 :             :  * with non-zero bytes, and shrinks the logical payload back to three
     330                 :             :  * elements while retaining the slab block. It then grows the buffer
     331                 :             :  * from three to eight elements with ZERO_NEW_MEMORY set. The tested
     332                 :             :  * grow stays inside the retained slab block, so it counts as one
     333                 :             :  * in-place resize. The library must zero exactly the newly exposed
     334                 :             :  * bytes [3..7] and bump zero_initialized_payload_growths by one. The
     335                 :             :  * newly exposed bytes are read back through m_data_ro to confirm that
     336                 :             :  * the previously non-zero storage was cleared
     337                 :             :  *
     338                 :             :  * @return Return describing success or failure
     339                 :             :  */
     340                 :           1 : static Return test_libmem_0009_08(void)
     341                 :             : {
     342                 :           1 :         INITTEST;
     343                 :             : 
     344                 :             :         /* Populate the reserved bytes that the tested grow will expose so
     345                 :             :            success cannot depend on previously zero allocator contents */
     346                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,8));
     347                 :             : 
     348                 :           1 :         unsigned char *raw = m_data(unsigned char,buffer);
     349                 :           1 :         ASSERT(raw != NULL);
     350                 :           1 :         IF(raw != NULL)
     351                 :             :         {
     352                 :           1 :                 raw[3] = 0xa3U;
     353                 :           1 :                 raw[4] = 0xa4U;
     354                 :           1 :                 raw[5] = 0xa5U;
     355                 :           1 :                 raw[6] = 0xa6U;
     356                 :           1 :                 raw[7] = 0xa7U;
     357                 :             :         }
     358                 :             : 
     359                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,3));
     360                 :             : 
     361                 :           1 :         const size_t baseline_zero_growths = telemetry.zero_initialized_payload_growths;
     362                 :           1 :         const size_t baseline_in_place = telemetry.in_place_resizes;
     363                 :           1 :         const size_t baseline_current_payload = telemetry.current_payload_bytes;
     364                 :             : 
     365                 :             :         /* ZERO_NEW_MEMORY must clear the retained non-zero payload tail */
     366                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,8,ZERO_NEW_MEMORY));
     367                 :             : 
     368                 :           1 :         ASSERT(telemetry.zero_initialized_payload_growths == baseline_zero_growths + 1);
     369                 :           1 :         ASSERT(telemetry.in_place_resizes == baseline_in_place + 1);
     370                 :           1 :         ASSERT(telemetry.current_payload_bytes == baseline_current_payload + 5);
     371                 :           1 :         ASSERT(buffer->length == 8);
     372                 :             : 
     373                 :           1 :         const unsigned char *view = m_data_ro(unsigned char,buffer);
     374                 :           1 :         ASSERT(view != NULL);
     375                 :           1 :         ASSERT(view[3] == 0);
     376                 :           1 :         ASSERT(view[4] == 0);
     377                 :           1 :         ASSERT(view[5] == 0);
     378                 :           1 :         ASSERT(view[6] == 0);
     379                 :           1 :         ASSERT(view[7] == 0);
     380                 :             : 
     381                 :           1 :         RETURN_STATUS;
     382                 :             : }
     383                 :             : 
     384                 :             : /**
     385                 :             :  * @brief Cover peak_active_descriptors at the entry of a second live descriptor
     386                 :             :  *
     387                 :             :  * Brings a second descriptor of a different element type into life
     388                 :             :  * while the first one is still active. After the fresh allocation,
     389                 :             :  * current_active_descriptors must be exactly two above the suite
     390                 :             :  * baseline. peak_active_descriptors is verified only as the basic
     391                 :             :  * peak >= current invariant, because the counter is global and
     392                 :             :  * monotonic across the runner: when earlier tests have already pushed
     393                 :             :  * the peak above the value reached here it stays unchanged, and only
     394                 :             :  * when this fresh acquisition sets a new high does peak track current.
     395                 :             :  * The test does not assert that this subtest itself lifted the peak.
     396                 :             :  * fresh_heap_allocations advances by one to count the second fresh
     397                 :             :  * acquisition
     398                 :             :  *
     399                 :             :  * @return Return describing success or failure
     400                 :             :  */
     401                 :           1 : static Return test_libmem_0009_09(void)
     402                 :             : {
     403                 :           1 :         INITTEST;
     404                 :             : 
     405                 :           1 :         const size_t baseline_fresh = telemetry.fresh_heap_allocations;
     406                 :           1 :         const size_t baseline_current_active = telemetry.current_active_descriptors;
     407                 :             : 
     408                 :           1 :         ASSERT(SUCCESS == m_resize(sentinel,1));
     409                 :             : 
     410                 :           1 :         ASSERT(telemetry.current_active_descriptors == baseline_current_active + 1);
     411                 :           1 :         ASSERT(telemetry.current_active_descriptors == suite_baseline.current_active_descriptors + 2);
     412                 :           1 :         ASSERT(telemetry.peak_active_descriptors >= telemetry.current_active_descriptors);
     413                 :           1 :         ASSERT(telemetry.fresh_heap_allocations == baseline_fresh + 1);
     414                 :             : 
     415                 :           1 :         ASSERT(sentinel->length == 1);
     416                 :           1 :         ASSERT(sentinel->data != NULL);
     417                 :             : 
     418                 :           1 :         RETURN_STATUS;
     419                 :             : }
     420                 :             : 
     421                 :             : /**
     422                 :             :  * @brief Cover arithmetic_guard_failures via the three guarded helpers
     423                 :             :  *
     424                 :             :  * Forces an overflow in m_guarded_byte_size, an overflow in
     425                 :             :  * m_guarded_add, and an underflow in m_guarded_subtract. Each
     426                 :             :  * guarded helper must reject the invalid input with FAILURE and bump
     427                 :             :  * both the actual and expected arithmetic_guard_failures counters by
     428                 :             :  * one, so each counter advances by exactly three relative to the entry baseline
     429                 :             :  *
     430                 :             :  * @return Return describing success or failure
     431                 :             :  */
     432                 :           1 : static Return test_libmem_0009_10(void)
     433                 :             : {
     434                 :           1 :         INITTEST;
     435                 :             : 
     436                 :           1 :         const size_t baseline_guard = telemetry.arithmetic_guard_failures;
     437                 :           1 :         const size_t baseline_expected_guard = telemetry.expected_arithmetic_guard_failures;
     438                 :           1 :         size_t scratch = 0;
     439                 :             : 
     440                 :           1 :         telemetry_expected_arithmetic_guard_failures();
     441                 :           1 :         ASSERT(FAILURE == m_guarded_byte_size(sentinel,SIZE_MAX,&scratch));
     442                 :           1 :         ASSERT(telemetry.arithmetic_guard_failures == baseline_guard + 1);
     443                 :           1 :         ASSERT(telemetry.expected_arithmetic_guard_failures == baseline_expected_guard + 1);
     444                 :             : 
     445                 :           1 :         telemetry_expected_arithmetic_guard_failures();
     446                 :           1 :         ASSERT(FAILURE == m_guarded_add(SIZE_MAX,1,&scratch));
     447                 :           1 :         ASSERT(telemetry.arithmetic_guard_failures == baseline_guard + 2);
     448                 :           1 :         ASSERT(telemetry.expected_arithmetic_guard_failures == baseline_expected_guard + 2);
     449                 :             : 
     450                 :           1 :         telemetry_expected_arithmetic_guard_failures();
     451                 :           1 :         ASSERT(FAILURE == m_guarded_subtract(0,1,&scratch));
     452                 :           1 :         ASSERT(telemetry.arithmetic_guard_failures == baseline_guard + 3);
     453                 :           1 :         ASSERT(telemetry.expected_arithmetic_guard_failures == baseline_expected_guard + 3);
     454                 :             : 
     455                 :           1 :         RETURN_STATUS;
     456                 :             : }
     457                 :             : 
     458                 :             : /**
     459                 :             :  * @brief Cover data_to_string_conversions through m_to_string
     460                 :             :  *
     461                 :             :  * Writes a known three-byte prefix into the buffer through m_data.
     462                 :             :  * The trailing zero bytes from the previous ZERO_NEW_MEMORY subtest
     463                 :             :  * let m_to_string measure string_length as three before it writes the
     464                 :             :  * canonical terminator at that boundary. m_to_string must flip the
     465                 :             :  * descriptor into string mode and bump data_to_string_conversions
     466                 :             :  * exactly once. The logical length stays at eight because the current
     467                 :             :  * descriptor span already has room for the visible payload and the
     468                 :             :  * terminator
     469                 :             :  *
     470                 :             :  * @return Return describing success or failure
     471                 :             :  */
     472                 :           1 : static Return test_libmem_0009_11(void)
     473                 :             : {
     474                 :           1 :         INITTEST;
     475                 :             : 
     476                 :           1 :         const size_t baseline_data_to_string = telemetry.data_to_string_conversions;
     477                 :             : 
     478                 :           1 :         unsigned char *raw = m_data(unsigned char,buffer);
     479                 :           1 :         ASSERT(raw != NULL);
     480                 :           1 :         IF(raw != NULL)
     481                 :             :         {
     482                 :           1 :                 raw[0] = (unsigned char)'A';
     483                 :           1 :                 raw[1] = (unsigned char)'B';
     484                 :           1 :                 raw[2] = (unsigned char)'C';
     485                 :             :         }
     486                 :             : 
     487                 :           1 :         ASSERT(SUCCESS == m_to_string(buffer));
     488                 :             : 
     489                 :           1 :         ASSERT(telemetry.data_to_string_conversions == baseline_data_to_string + 1);
     490                 :           1 :         ASSERT(buffer->is_string == true);
     491                 :           1 :         ASSERT(buffer->string_length == 3);
     492                 :           1 :         ASSERT(buffer->length == 8);
     493                 :             : 
     494                 :           1 :         RETURN_STATUS;
     495                 :             : }
     496                 :             : 
     497                 :             : /**
     498                 :             :  * @brief Cover string_to_data_conversions through m_to_data
     499                 :             :  *
     500                 :             :  * Converts the buffer back to data mode. The cached string length is
     501                 :             :  * three while the logical length is eight, so the trailing-terminator
     502                 :             :  * trim path does not fire and the logical length stays at eight.
     503                 :             :  * string_to_data_conversions advances by one and is_string flips
     504                 :             :  * back to false while string_length resets to zero
     505                 :             :  *
     506                 :             :  * @return Return describing success or failure
     507                 :             :  */
     508                 :           1 : static Return test_libmem_0009_12(void)
     509                 :             : {
     510                 :           1 :         INITTEST;
     511                 :             : 
     512                 :           1 :         const size_t baseline_string_to_data = telemetry.string_to_data_conversions;
     513                 :             : 
     514                 :           1 :         ASSERT(SUCCESS == m_to_data(buffer));
     515                 :             : 
     516                 :           1 :         ASSERT(telemetry.string_to_data_conversions == baseline_string_to_data + 1);
     517                 :           1 :         ASSERT(buffer->is_string == false);
     518                 :           1 :         ASSERT(buffer->string_length == 0);
     519                 :           1 :         ASSERT(buffer->length == 8);
     520                 :             : 
     521                 :           1 :         RETURN_STATUS;
     522                 :             : }
     523                 :             : 
     524                 :             : /**
     525                 :             :  * @brief Cover m_resize(...,0) without RELEASE_UNUSED preserving the reserve
     526                 :             :  *
     527                 :             :  * Snapshots the current data pointer and reserved byte count before
     528                 :             :  * the call. After m_resize(buffer,0) the descriptor must report a
     529                 :             :  * zero logical length and a zero string_length while still pointing
     530                 :             :  * at the very same allocation as before, with actually_allocated_bytes
     531                 :             :  * unchanged. heap_buffer_releases must stay at the entry baseline
     532                 :             :  * because no memory has actually been handed back to the OS yet, and
     533                 :             :  * release_unused_shrinks must also stay put because the call did not
     534                 :             :  * carry the RELEASE_UNUSED flag. The previous logical payload (eight
     535                 :             :  * bytes) is reclassified as block overhead because the slab reserve is
     536                 :             :  * retained while the logical length drops to zero, so
     537                 :             :  * current_block_overhead_bytes and total_block_overhead_bytes_added
     538                 :             :  * each grow by exactly eight bytes and peak_block_overhead_bytes is
     539                 :             :  * lifted whenever the new value exceeds the previous peak
     540                 :             :  *
     541                 :             :  * @return Return describing success or failure
     542                 :             :  */
     543                 :           1 : static Return test_libmem_0009_13(void)
     544                 :             : {
     545                 :           1 :         INITTEST;
     546                 :             : 
     547                 :           1 :         const size_t baseline_current_heap = telemetry.current_heap_reserved_bytes;
     548                 :           1 :         const size_t baseline_current_payload = telemetry.current_payload_bytes;
     549                 :           1 :         const size_t baseline_releases = telemetry.heap_buffer_releases;
     550                 :           1 :         const size_t baseline_release_shrinks = telemetry.release_unused_shrinks;
     551                 :           1 :         const size_t baseline_total_release_unused = telemetry.total_release_unused_heap_reserved_bytes_released;
     552                 :           1 :         const size_t baseline_current_active = telemetry.current_active_descriptors;
     553                 :           1 :         const size_t baseline_current_block_overhead = telemetry.current_block_overhead_bytes;
     554                 :           1 :         const size_t baseline_total_block_overhead_added = telemetry.total_block_overhead_bytes_added;
     555                 :             : 
     556                 :           1 :         void * const retained_data = buffer->data;
     557                 :           1 :         const size_t retained_bytes = buffer->actually_allocated_bytes;
     558                 :             : 
     559                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,0));
     560                 :             : 
     561                 :           1 :         ASSERT(buffer->data == retained_data);
     562                 :           1 :         ASSERT(buffer->actually_allocated_bytes == retained_bytes);
     563                 :           1 :         ASSERT(buffer->length == 0);
     564                 :           1 :         ASSERT(buffer->string_length == 0);
     565                 :           1 :         ASSERT(buffer->is_string == false);
     566                 :             : 
     567                 :           1 :         ASSERT(telemetry.current_heap_reserved_bytes == baseline_current_heap);
     568                 :           1 :         ASSERT(telemetry.current_payload_bytes == baseline_current_payload - 8);
     569                 :           1 :         ASSERT(telemetry.current_active_descriptors == baseline_current_active);
     570                 :           1 :         ASSERT(telemetry.heap_buffer_releases == baseline_releases);
     571                 :           1 :         ASSERT(telemetry.release_unused_shrinks == baseline_release_shrinks);
     572                 :           1 :         ASSERT(telemetry.total_release_unused_heap_reserved_bytes_released == baseline_total_release_unused);
     573                 :             : 
     574                 :           1 :         ASSERT(telemetry.current_block_overhead_bytes == baseline_current_block_overhead + 8);
     575                 :           1 :         ASSERT(telemetry.total_block_overhead_bytes_added == baseline_total_block_overhead_added + 8);
     576                 :           1 :         ASSERT(telemetry.peak_block_overhead_bytes >= telemetry.current_block_overhead_bytes);
     577                 :             : 
     578                 :           1 :         RETURN_STATUS;
     579                 :             : }
     580                 :             : 
     581                 :             : /**
     582                 :             :  * @brief Cover m_resize(...,0,RELEASE_UNUSED) returning the buffer to the OS
     583                 :             :  *
     584                 :             :  * The capacity that was retained in the previous subtest is now
     585                 :             :  * released. The descriptor must drop its data pointer, drop the
     586                 :             :  * actually_allocated_bytes, count one heap_buffer_releases for the
     587                 :             :  * physical free, advance release_unused_shrinks by one, and add the
     588                 :             :  * released slab bytes to total_release_unused_heap_reserved_bytes_released.
     589                 :             :  * Active descriptors decrement by one because the sentinel is still
     590                 :             :  * alive
     591                 :             :  *
     592                 :             :  * @return Return describing success or failure
     593                 :             :  */
     594                 :           1 : static Return test_libmem_0009_14(void)
     595                 :             : {
     596                 :           1 :         INITTEST;
     597                 :             : 
     598                 :           1 :         const size_t block = MEMORY_BLOCK_BYTES;
     599                 :             : 
     600                 :           1 :         const size_t baseline_current_heap = telemetry.current_heap_reserved_bytes;
     601                 :           1 :         const size_t baseline_releases = telemetry.heap_buffer_releases;
     602                 :           1 :         const size_t baseline_release_shrinks = telemetry.release_unused_shrinks;
     603                 :           1 :         const size_t baseline_total_release_unused = telemetry.total_release_unused_heap_reserved_bytes_released;
     604                 :           1 :         const size_t baseline_total_released = telemetry.total_heap_reserved_bytes_released;
     605                 :           1 :         const size_t baseline_current_active = telemetry.current_active_descriptors;
     606                 :             : 
     607                 :           1 :         ASSERT(SUCCESS == m_resize(buffer,0,RELEASE_UNUSED));
     608                 :             : 
     609                 :           1 :         ASSERT(buffer->data == NULL);
     610                 :           1 :         ASSERT(buffer->actually_allocated_bytes == 0);
     611                 :           1 :         ASSERT(buffer->length == 0);
     612                 :           1 :         ASSERT(buffer->string_length == 0);
     613                 :           1 :         ASSERT(buffer->is_string == false);
     614                 :             : 
     615                 :           1 :         ASSERT(telemetry.current_heap_reserved_bytes == baseline_current_heap - block);
     616                 :           1 :         ASSERT(telemetry.current_active_descriptors == baseline_current_active - 1);
     617                 :           1 :         ASSERT(telemetry.heap_buffer_releases == baseline_releases + 1);
     618                 :           1 :         ASSERT(telemetry.release_unused_shrinks == baseline_release_shrinks + 1);
     619                 :           1 :         ASSERT(telemetry.total_release_unused_heap_reserved_bytes_released == baseline_total_release_unused + block);
     620                 :           1 :         ASSERT(telemetry.total_heap_reserved_bytes_released == baseline_total_released + block);
     621                 :             : 
     622                 :           1 :         RETURN_STATUS;
     623                 :             : }
     624                 :             : 
     625                 :             : /**
     626                 :             :  * @brief Cover descriptor teardown and assert the suite-wide post-conditions
     627                 :             :  *
     628                 :             :  * Calls m_del on the already-empty buffer, which must be accepted
     629                 :             :  * without bumping any counter, and then calls m_del on the sentinel,
     630                 :             :  * which actually frees the sentinel's slab block. After the calls every
     631                 :             :  * "current" counter must be back to its suite baseline value, the
     632                 :             :  * "total" counters must be at least at their suite baseline values,
     633                 :             :  * and peak_consecutive_noop_resizes must be at least three because subtest
     634                 :             :  * 06 directly drives that consecutive no-op count inside the suite. The
     635                 :             :  * remaining peak
     636                 :             :  * counters (peak_heap_reserved_bytes, peak_block_overhead_bytes,
     637                 :             :  * peak_active_descriptors) are global and monotonic across the runner,
     638                 :             :  * so this finalization step does not re-check them. Allocator-failure
     639                 :             :  * counters (heap_allocation_failures, heap_reallocation_failures) are
     640                 :             :  * exercised by dedicated subtests 17 and 18 through the libmem
     641                 :             :  * allocator mock, not by this finalization step
     642                 :             :  *
     643                 :             :  * @return Return describing success or failure
     644                 :             :  */
     645                 :           1 : static Return test_libmem_0009_15(void)
     646                 :             : {
     647                 :           1 :         INITTEST;
     648                 :             : 
     649                 :           1 :         const size_t block = MEMORY_BLOCK_BYTES;
     650                 :             : 
     651                 :             :         /* m_del on an already-empty descriptor enters mem_delete with
     652                 :             :            data == NULL and must skip every counter-touching branch, so the
     653                 :             :            whole Telemetry struct has to stay byte-for-byte unchanged */
     654                 :           1 :         const Telemetry before_buffer_del = telemetry;
     655                 :             : 
     656                 :           1 :         call(m_del(buffer));
     657                 :             : 
     658                 :           1 :         ASSERT(buffer->length == 0);
     659                 :           1 :         ASSERT(buffer->string_length == 0);
     660                 :           1 :         ASSERT(buffer->is_string == false);
     661                 :           1 :         ASSERT(buffer->data == NULL);
     662                 :           1 :         ASSERT(buffer->actually_allocated_bytes == 0);
     663                 :           1 :         ASSERT(memcmp(&telemetry,&before_buffer_del,sizeof(Telemetry)) == 0);
     664                 :             : 
     665                 :             :         /* m_del on the still-allocated sentinel must move exactly the
     666                 :             :            counters that mem_delete touches when data != NULL: the heap
     667                 :             :            reserve and active-descriptor counts decrement, the
     668                 :             :            release-side totals advance by one slab block, and the
     669                 :             :            payload and block-overhead bookkeeping for the sentinel's
     670                 :             :            four-byte logical content is rolled back. Building the
     671                 :             :            expected Telemetry from the pre-state and asserting full-struct
     672                 :             :            equality verifies that no other counter slipped */
     673                 :           1 :         const Telemetry before_sentinel_del = telemetry;
     674                 :           1 :         const size_t sentinel_payload_bytes = sizeof(uint32_t);
     675                 :           1 :         const size_t sentinel_block_overhead_bytes = block - sentinel_payload_bytes;
     676                 :             : 
     677                 :           1 :         call(m_del(sentinel));
     678                 :             : 
     679                 :           1 :         ASSERT(sentinel->length == 0);
     680                 :           1 :         ASSERT(sentinel->string_length == 0);
     681                 :           1 :         ASSERT(sentinel->is_string == false);
     682                 :           1 :         ASSERT(sentinel->data == NULL);
     683                 :           1 :         ASSERT(sentinel->actually_allocated_bytes == 0);
     684                 :             : 
     685                 :           1 :         Telemetry expected_after_sentinel_del = before_sentinel_del;
     686                 :           1 :         expected_after_sentinel_del.current_heap_reserved_bytes -= block;
     687                 :           1 :         expected_after_sentinel_del.total_heap_reserved_bytes_released += block;
     688                 :           1 :         expected_after_sentinel_del.heap_buffer_releases += 1;
     689                 :           1 :         expected_after_sentinel_del.current_active_descriptors -= 1;
     690                 :           1 :         expected_after_sentinel_del.current_payload_bytes -= sentinel_payload_bytes;
     691                 :           1 :         expected_after_sentinel_del.current_block_overhead_bytes -= sentinel_block_overhead_bytes;
     692                 :             : 
     693                 :           1 :         ASSERT(memcmp(&telemetry,&expected_after_sentinel_del,sizeof(Telemetry)) == 0);
     694                 :             : 
     695                 :           1 :         ASSERT(telemetry.current_heap_reserved_bytes == suite_baseline.current_heap_reserved_bytes);
     696                 :           1 :         ASSERT(telemetry.current_payload_bytes == suite_baseline.current_payload_bytes);
     697                 :           1 :         ASSERT(telemetry.current_active_descriptors == suite_baseline.current_active_descriptors);
     698                 :           1 :         ASSERT(telemetry.current_block_overhead_bytes == suite_baseline.current_block_overhead_bytes);
     699                 :           1 :         ASSERT(telemetry.current_consecutive_noop_resizes == 0);
     700                 :             : 
     701                 :             :         /* Peak counters are global and monotonic across the whole process,
     702                 :             :            so when the suite runs alongside other tests an earlier high mark
     703                 :             :            can already exceed anything this suite produces. The meaningful
     704                 :             :            peak driven by the suite itself (the three consecutive no-op resizes) is
     705                 :             :            verified directly. The remaining peak counters
     706                 :             :            (peak_heap_reserved_bytes, peak_block_overhead_bytes,
     707                 :             :            peak_active_descriptors) are not asserted here because no subtest
     708                 :             :            enforces a suite-driven lift: subtests 02 and 09 check only the
     709                 :             :            peak >= current invariant, and the full-struct memcmp in
     710                 :             :            subtest 03 pins peak to its conditional post-state without
     711                 :             :            requiring the suite to be the source of the maximum */
     712                 :           1 :         ASSERT(telemetry.peak_consecutive_noop_resizes >= 3);
     713                 :             : 
     714                 :             :         /* The runner framework (testitall) may allocate and free its own
     715                 :             :            STDOUT, STDERR, and EXTEND capture descriptors between subtests,
     716                 :             :            so the monotonic byte and "fresh"/"release" counters can carry
     717                 :             :            framework contributions on top of the test-driven moves. The
     718                 :             :            asserts below use >= for those counters and == for the counters
     719                 :             :            that only this suite can move here: mode conversions,
     720                 :             :            guarded-arithmetic failures, RELEASE_UNUSED counters,
     721                 :             :            ZERO_NEW_MEMORY growths, and no-op resizes */
     722                 :           1 :         ASSERT(telemetry.total_heap_reserved_bytes_acquired >= suite_baseline.total_heap_reserved_bytes_acquired + block * 3);
     723                 :           1 :         ASSERT(telemetry.total_heap_reserved_bytes_released >= suite_baseline.total_heap_reserved_bytes_released + block * 3);
     724                 :           1 :         ASSERT(telemetry.total_release_unused_heap_reserved_bytes_released == suite_baseline.total_release_unused_heap_reserved_bytes_released + block * 2);
     725                 :             : 
     726                 :           1 :         ASSERT(telemetry.fresh_heap_allocations >= suite_baseline.fresh_heap_allocations + 2);
     727                 :           1 :         ASSERT(telemetry.heap_reallocations >= suite_baseline.heap_reallocations + 2);
     728                 :           1 :         ASSERT(telemetry.in_place_resizes >= suite_baseline.in_place_resizes + 5);
     729                 :           1 :         ASSERT(telemetry.noop_resizes == suite_baseline.noop_resizes + 3);
     730                 :           1 :         ASSERT(telemetry.release_unused_shrinks == suite_baseline.release_unused_shrinks + 2);
     731                 :           1 :         ASSERT(telemetry.heap_buffer_releases >= suite_baseline.heap_buffer_releases + 2);
     732                 :           1 :         ASSERT(telemetry.zero_initialized_payload_growths == suite_baseline.zero_initialized_payload_growths + 1);
     733                 :             : 
     734                 :           1 :         ASSERT(telemetry.data_to_string_conversions == suite_baseline.data_to_string_conversions + 1);
     735                 :           1 :         ASSERT(telemetry.string_to_data_conversions == suite_baseline.string_to_data_conversions + 1);
     736                 :           1 :         ASSERT(telemetry.arithmetic_guard_failures == suite_baseline.arithmetic_guard_failures + 3);
     737                 :           1 :         ASSERT(telemetry.expected_arithmetic_guard_failures == suite_baseline.expected_arithmetic_guard_failures + 3);
     738                 :             : 
     739                 :             :         /* mem_write_zero_terminator centralized helper fires for every
     740                 :             :            successful terminator write across the library. Up to this
     741                 :             :            checkpoint, before the dedicated finalize-string subtest runs,
     742                 :             :            the only suite-guaranteed write is the terminator placement
     743                 :             :            during subtest 11's m_to_string flip. The runner framework drives
     744                 :             :            additional writes between subtests, so the assert uses >= rather
     745                 :             :            than == */
     746                 :           1 :         ASSERT(telemetry.string_terminator_writes >= suite_baseline.string_terminator_writes + 1);
     747                 :             : 
     748                 :             :         #if SHOW_TEST
     749                 :             :         telemetry_summary();
     750                 :             :         #endif
     751                 :             : 
     752                 :           1 :         RETURN_STATUS;
     753                 :             : }
     754                 :             : 
     755                 :             : /**
     756                 :             :  * @brief Cover the two finalize-string terminator branches under WRITE_TERMINATOR_IF_MISSING and the centralized terminator-write counter
     757                 :             :  *
     758                 :             :  * Drives a fresh local string descriptor through three cases of
     759                 :             :  * m_finalize_string(). The first case places a zero element at the
     760                 :             :  * boundary slot, so finalize must skip the write and bump
     761                 :             :  * finalize_string_terminator_already_present by one while leaving
     762                 :             :  * finalize_string_terminator_written_when_missing and
     763                 :             :  * string_terminator_writes untouched. The second case places a
     764                 :             :  * non-zero element at the boundary slot, so finalize must write the
     765                 :             :  * terminator itself and bump finalize_string_terminator_written_when_missing
     766                 :             :  * and string_terminator_writes by one each while leaving
     767                 :             :  * finalize_string_terminator_already_present untouched. The third
     768                 :             :  * case forces WRITE_TERMINATOR_ALWAYS to confirm that branch does
     769                 :             :  * not move either IF_MISSING counter but still bumps
     770                 :             :  * string_terminator_writes by one because the centralized helper
     771                 :             :  * ran the memset
     772                 :             :  *
     773                 :             :  * @return Return describing success or failure
     774                 :             :  */
     775                 :           1 : static Return test_libmem_0009_16(void)
     776                 :             : {
     777                 :           1 :         INITTEST;
     778                 :             : 
     779                 :           1 :         const size_t baseline_already_present = telemetry.finalize_string_terminator_already_present;
     780                 :           1 :         const size_t baseline_written_when_missing = telemetry.finalize_string_terminator_written_when_missing;
     781                 :             : 
     782                 :             :         /* Local string descriptor with capacity for two visible payload elements
     783                 :             :            and one terminator slot. Owned by this subtest so the suite finalization
     784                 :             :            in test_libmem_0009_15 stays unaffected by the activity here */
     785                 :           1 :         m_create(char,string_buffer,MEMORY_STRING);
     786                 :             : 
     787                 :           1 :         ASSERT(SUCCESS == m_resize(string_buffer,3));
     788                 :           1 :         ASSERT(string_buffer->is_string == true);
     789                 :           1 :         ASSERT(string_buffer->length == 3);
     790                 :             : 
     791                 :             :         /* Capture string_terminator_writes after m_resize so the per-case
     792                 :             :            asserts below depend only on the three explicit m_finalize_string
     793                 :             :            calls below and not on whatever the string-mode resize did
     794                 :             :            internally through mem_string_truncate */
     795                 :           1 :         const size_t baseline_terminator_writes = telemetry.string_terminator_writes;
     796                 :             : 
     797                 :           1 :         char *raw = m_data(char,string_buffer);
     798                 :           1 :         ASSERT(raw != NULL);
     799                 :             : 
     800                 :             :         /* Case 16a: slot already holds a zero element, IF_MISSING skips the write */
     801                 :           1 :         IF(raw != NULL)
     802                 :             :         {
     803                 :           1 :                 raw[0] = 'A';
     804                 :           1 :                 raw[1] = 'B';
     805                 :           1 :                 raw[2] = '\0';
     806                 :             :         }
     807                 :             : 
     808                 :           1 :         ASSERT(SUCCESS == m_finalize_string(string_buffer,2,WRITE_TERMINATOR_IF_MISSING));
     809                 :           1 :         ASSERT(telemetry.finalize_string_terminator_already_present == baseline_already_present + 1);
     810                 :           1 :         ASSERT(telemetry.finalize_string_terminator_written_when_missing == baseline_written_when_missing);
     811                 :           1 :         ASSERT(telemetry.string_terminator_writes == baseline_terminator_writes);
     812                 :           1 :         ASSERT(string_buffer->string_length == 2);
     813                 :             : 
     814                 :             :         /* Case 16b: slot holds a non-zero element, IF_MISSING compensates by writing */
     815                 :           1 :         IF(raw != NULL)
     816                 :             :         {
     817                 :           1 :                 raw[0] = 'C';
     818                 :           1 :                 raw[1] = 'D';
     819                 :           1 :                 raw[2] = 'X';
     820                 :             :         }
     821                 :             : 
     822                 :           1 :         ASSERT(SUCCESS == m_finalize_string(string_buffer,2,WRITE_TERMINATOR_IF_MISSING));
     823                 :           1 :         ASSERT(telemetry.finalize_string_terminator_already_present == baseline_already_present + 1);
     824                 :           1 :         ASSERT(telemetry.finalize_string_terminator_written_when_missing == baseline_written_when_missing + 1);
     825                 :           1 :         ASSERT(telemetry.string_terminator_writes == baseline_terminator_writes + 1);
     826                 :           1 :         ASSERT(string_buffer->string_length == 2);
     827                 :           1 :         ASSERT(raw[2] == '\0');
     828                 :             : 
     829                 :             :         /* Case 16c: WRITE_TERMINATOR_ALWAYS writes unconditionally and must
     830                 :             :            not move either of the new IF_MISSING counters */
     831                 :           1 :         IF(raw != NULL)
     832                 :             :         {
     833                 :           1 :                 raw[0] = 'E';
     834                 :           1 :                 raw[1] = 'F';
     835                 :           1 :                 raw[2] = 'Y';
     836                 :             :         }
     837                 :             : 
     838                 :           1 :         ASSERT(SUCCESS == m_finalize_string(string_buffer,2,WRITE_TERMINATOR_ALWAYS));
     839                 :           1 :         ASSERT(telemetry.finalize_string_terminator_already_present == baseline_already_present + 1);
     840                 :           1 :         ASSERT(telemetry.finalize_string_terminator_written_when_missing == baseline_written_when_missing + 1);
     841                 :           1 :         ASSERT(telemetry.string_terminator_writes == baseline_terminator_writes + 2);
     842                 :           1 :         ASSERT(string_buffer->string_length == 2);
     843                 :           1 :         ASSERT(raw[2] == '\0');
     844                 :             : 
     845                 :           1 :         call(m_del(string_buffer));
     846                 :             : 
     847                 :           1 :         RETURN_STATUS;
     848                 :             : }
     849                 :             : 
     850                 :             : #ifndef EVIL_EMPIRE_OS
     851                 :             : /**
     852                 :             :  * @brief Capture function for subtest 17 — force malloc to return NULL inside mem_resize
     853                 :             :  *
     854                 :             :  * Activates the libmem allocator mock for one malloc call, then asks
     855                 :             :  * m_resize to grow a fresh descriptor from zero to one element. The
     856                 :             :  * expected-failure marker is advanced immediately before the forced
     857                 :             :  * call. The library must observe the NULL return, bump heap_allocation_failures
     858                 :             :  * by one, and leave the descriptor in a clean unallocated state. Runs through
     859                 :             :  * match_function_output in the driver, so the report() emission that mem_resize
     860                 :             :  * makes lands in captured stderr instead of polluting the suite output
     861                 :             :  *
     862                 :             :  * @return Return describing success or failure
     863                 :             :  */
     864                 :           1 : static Return capture_libmem_0009_17_malloc_fail(void)
     865                 :             : {
     866                 :           1 :         INITTEST;
     867                 :             : 
     868                 :           1 :         const size_t baseline = telemetry.heap_allocation_failures;
     869                 :           1 :         const size_t baseline_expected = telemetry.expected_heap_allocation_failures;
     870                 :             : 
     871                 :           1 :         m_create(char,fail_buf,MEMORY_STRING);
     872                 :             : 
     873                 :           1 :         testmocking_malloc_fail_next(1);
     874                 :           1 :         telemetry_expected_heap_allocation_failures();
     875                 :           1 :         const Return result = m_resize(fail_buf,1);
     876                 :           1 :         testmocking_malloc_disable();
     877                 :             : 
     878                 :           1 :         ASSERT(FAILURE == result);
     879                 :           1 :         ASSERT(telemetry.heap_allocation_failures == baseline + 1);
     880                 :           1 :         ASSERT(telemetry.expected_heap_allocation_failures == baseline_expected + 1);
     881                 :           1 :         ASSERT(fail_buf->data == NULL);
     882                 :           1 :         ASSERT(fail_buf->length == 0);
     883                 :           1 :         ASSERT(fail_buf->actually_allocated_bytes == 0);
     884                 :             : 
     885                 :           1 :         deliver(status);
     886                 :             : }
     887                 :             : 
     888                 :             : /**
     889                 :             :  * @brief Cover heap_allocation_failures by forcing malloc to return NULL
     890                 :             :  *
     891                 :             :  * Runs capture_libmem_0009_17_malloc_fail under match_function_output
     892                 :             :  * so the expected report() output from mem_resize stays inside captured
     893                 :             :  * stderr and away from the visible suite log. The driver asserts that
     894                 :             :  * stdout was silent, that stderr matches the expected single ERROR line
     895                 :             :  * for a 4096-byte malloc failure (line-number flexible, errno
     896                 :             :  * description system-dependent). Evil Empire OS builds exclude this subtest
     897                 :             :  * because Apple ld does not honor -Wl,--wrap, so the mock cannot fire there
     898                 :             :  * and the assertions would not hold
     899                 :             :  *
     900                 :             :  * @return Return describing success or failure
     901                 :             :  */
     902                 :           1 : static Return test_libmem_0009_17(void)
     903                 :             : {
     904                 :           1 :         INITTEST;
     905                 :             : 
     906                 :             :         /* Expected stderr layout for subtest 17. mem_resize is forced to a
     907                 :             :            failed malloc by the libmem allocator mock and is expected to emit a
     908                 :             :            single report() line. The pattern leaves the source line number
     909                 :             :            flexible with \\d+ and the errno description flexible with [^\\n]+,
     910                 :             :            but pins the message body byte for byte */
     911                 :             :         static const char expected_stderr_pattern_libmem_0009_17[] =
     912                 :             :                 "\\A"
     913                 :             :                 "ERROR: src/mem_resize\\.c:mem_resize:\\d+ Memory management; Memory allocation failed for 4096 bytes Errno: [^\\n]+ \\(errno: [0-9]+\\)\n"
     914                 :             :                 "\\Z";
     915                 :             : 
     916                 :           1 :         ASSERT(SUCCESS == match_function_output(NULL,expected_stderr_pattern_libmem_0009_17,capture_libmem_0009_17_malloc_fail));
     917                 :             : 
     918                 :           1 :         RETURN_STATUS;
     919                 :             : }
     920                 :             : 
     921                 :             : /**
     922                 :             :  * @brief Capture function for subtest 18 — force realloc to return NULL inside mem_resize
     923                 :             :  *
     924                 :             :  * First grows a fresh descriptor through a successful initial malloc so
     925                 :             :  * the next size change becomes a realloc instead of a fresh allocation.
     926                 :             :  * Then activates the realloc mock for one call and asks m_resize to
     927                 :             :  * grow past one slab block, which forces the library to call realloc.
     928                 :             :  * The expected-failure marker is advanced immediately before that forced
     929                 :             :  * call. The library must observe the NULL return, bump
     930                 :             :  * heap_reallocation_failures by one, leave heap_allocation_failures
     931                 :             :  * untouched, and keep the descriptor's previous allocation valid
     932                 :             :  * because realloc returning NULL does not free the original block.
     933                 :             :  * Runs through match_function_output in the driver
     934                 :             :  *
     935                 :             :  * @return Return describing success or failure
     936                 :             :  */
     937                 :           1 : static Return capture_libmem_0009_18_realloc_fail(void)
     938                 :             : {
     939                 :           1 :         INITTEST;
     940                 :             : 
     941                 :           1 :         const size_t baseline_alloc = telemetry.heap_allocation_failures;
     942                 :           1 :         const size_t baseline_realloc = telemetry.heap_reallocation_failures;
     943                 :           1 :         const size_t baseline_expected_realloc = telemetry.expected_heap_reallocation_failures;
     944                 :             : 
     945                 :           1 :         m_create(char,grow_buf,MEMORY_STRING);
     946                 :             : 
     947                 :           1 :         ASSERT(SUCCESS == m_resize(grow_buf,1));
     948                 :             : 
     949                 :           1 :         testmocking_realloc_fail_next(1);
     950                 :           1 :         telemetry_expected_heap_reallocation_failures();
     951                 :           1 :         const Return result = m_resize(grow_buf,MEMORY_BLOCK_BYTES + 1);
     952                 :           1 :         testmocking_realloc_disable();
     953                 :             : 
     954                 :           1 :         ASSERT(FAILURE == result);
     955                 :           1 :         ASSERT(telemetry.heap_reallocation_failures == baseline_realloc + 1);
     956                 :           1 :         ASSERT(telemetry.expected_heap_reallocation_failures == baseline_expected_realloc + 1);
     957                 :           1 :         ASSERT(telemetry.heap_allocation_failures == baseline_alloc);
     958                 :           1 :         ASSERT(grow_buf->data != NULL);
     959                 :           1 :         ASSERT(grow_buf->length == 1);
     960                 :             : 
     961                 :           1 :         call(m_del(grow_buf));
     962                 :             : 
     963                 :           1 :         deliver(status);
     964                 :             : }
     965                 :             : 
     966                 :             : /**
     967                 :             :  * @brief Cover heap_reallocation_failures by forcing realloc to return NULL
     968                 :             :  *
     969                 :             :  * Runs capture_libmem_0009_18_realloc_fail under match_function_output
     970                 :             :  * so the expected report() output from mem_resize stays inside captured
     971                 :             :  * stderr. The driver asserts that stdout was silent and that stderr
     972                 :             :  * matches the expected single ERROR line for an 8192-byte realloc failure.
     973                 :             :  * Evil Empire OS builds exclude this subtest for the same reason as subtest 17
     974                 :             :  *
     975                 :             :  * @return Return describing success or failure
     976                 :             :  */
     977                 :           1 : static Return test_libmem_0009_18(void)
     978                 :             : {
     979                 :           1 :         INITTEST;
     980                 :             : 
     981                 :             :         /* Expected stderr layout for subtest 18. The realloc path inside
     982                 :             :            mem_resize prints the same report() format as the malloc path, only
     983                 :             :            the slab-rounded byte count differs */
     984                 :             :         static const char expected_stderr_pattern_libmem_0009_18[] =
     985                 :             :                 "\\A"
     986                 :             :                 "ERROR: src/mem_resize\\.c:mem_resize:\\d+ Memory management; Memory allocation failed for 8192 bytes Errno: [^\\n]+ \\(errno: [0-9]+\\)\n"
     987                 :             :                 "\\Z";
     988                 :             : 
     989                 :           1 :         ASSERT(SUCCESS == match_function_output(NULL,expected_stderr_pattern_libmem_0009_18,capture_libmem_0009_18_realloc_fail));
     990                 :             : 
     991                 :           1 :         RETURN_STATUS;
     992                 :             : }
     993                 :             : #endif
     994                 :             : 
     995                 :             : /**
     996                 :             :  * @brief End-to-end suite that exercises every libmem telemetry counter at least once
     997                 :             :  *
     998                 :             :  * Drives a shared unsigned-char descriptor and an auxiliary uint32_t
     999                 :             :  * descriptor through every transition that a Telemetry counter
    1000                 :             :  * observes. Each counter in the Telemetry struct is exercised at least
    1001                 :             :  * once by the actions of one of the subtests below and is then
    1002                 :             :  * asserted as a delta relative to the entry baseline of the
    1003                 :             :  * corresponding subtest, so the suite serves as the canonical proof
    1004                 :             :  * that telemetry stays consistent with descriptor state across every
    1005                 :             :  * supported transition. On builds that support link-time wrappers,
    1006                 :             :  * allocator-failure counters (heap_allocation_failures,
    1007                 :             :  * heap_reallocation_failures) are driven deterministically by the libmem
    1008                 :             :  * allocator mock in subtests 17 and 18
    1009                 :             :  *
    1010                 :             :  * @return Return describing success or failure
    1011                 :             :  */
    1012                 :           1 : Return test_libmem_0009(void)
    1013                 :             : {
    1014                 :           1 :         INITTEST;
    1015                 :             : 
    1016                 :           1 :         TEST(test_libmem_0009_01,"Suite baseline captured and shared descriptors reset");
    1017                 :           1 :         TEST(test_libmem_0009_02,"First allocation populates fresh heap and payload counters");
    1018                 :           1 :         TEST(test_libmem_0009_03,"Growth across a slab boundary registers a heap reallocation");
    1019                 :           1 :         TEST(test_libmem_0009_04,"RELEASE_UNUSED shrink returns one slab block to the OS");
    1020                 :           1 :         TEST(test_libmem_0009_05,"In-place grow inside the retained slab block bumps in_place_resizes");
    1021                 :           1 :         TEST(test_libmem_0009_06,"Three consecutive no-op resizes reach a count of three");
    1022                 :           1 :         TEST(test_libmem_0009_07,"Effective resize resets consecutive no-op counting and preserves the peak");
    1023                 :           1 :         TEST(test_libmem_0009_08,"ZERO_NEW_MEMORY growth zero-fills the newly exposed payload");
    1024                 :           1 :         TEST(test_libmem_0009_09,"Second live descriptor brings active count to two above the suite baseline");
    1025                 :           1 :         TEST(test_libmem_0009_10,"Guarded arithmetic helpers reject invalid math three times");
    1026                 :           1 :         TEST(test_libmem_0009_11,"m_to_string flips the buffer into string mode");
    1027                 :           1 :         TEST(test_libmem_0009_12,"m_to_data flips the buffer back into data mode");
    1028                 :           1 :         TEST(test_libmem_0009_13,"m_resize to zero without flags keeps the underlying block");
    1029                 :           1 :         TEST(test_libmem_0009_14,"m_resize to zero with RELEASE_UNUSED frees the buffer");
    1030                 :           1 :         TEST(test_libmem_0009_15,"m_del tears down both descriptors and finalizes the suite");
    1031                 :           1 :         TEST(test_libmem_0009_16,"m_finalize_string IF_MISSING records present-vs-written terminator counters");
    1032                 :             : #ifndef EVIL_EMPIRE_OS
    1033                 :           1 :         TEST(test_libmem_0009_17,"Allocator returns NULL on initial malloc and bumps heap_allocation_failures");
    1034                 :           1 :         TEST(test_libmem_0009_18,"Allocator returns NULL on grow realloc and bumps heap_reallocation_failures");
    1035                 :             : #endif
    1036                 :             : 
    1037                 :           1 :         RETURN_STATUS;
    1038                 :             : }
        

Generated by: LCOV version 2.0-1