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 : : }
|