Skip to content

Commit 7b5c198

Browse files
add a more complex test
1 parent 3261204 commit 7b5c198

File tree

2 files changed

+205
-15
lines changed

2 files changed

+205
-15
lines changed

libudpard/udpard.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -855,14 +855,14 @@ static rx_fragment_tree_update_result_t rx_fragment_tree_update(udpard_tree_t**
855855
}
856856

857857
// The addition of a larger fragment that joins adjacent fragments together into a larger contiguous block may
858-
// render smaller fragments that overlap with its edges redundant.
858+
// render smaller fragments crossing its boundaries redundant.
859859
// To check for that, we create a new virtual fragment that represents the new fragment together with those
860-
// that join it on either end, if any, and then look for fragments contained within.
860+
// that join it on either end, if any, and then look for fragments contained within the virtual one.
861861
// Example:
862862
// |--B--|
863863
// |--X--|
864864
// |--A--|
865-
// The addition of fragment A or B will render X redundant, even though it is not contained within any fragment.
865+
// The addition of fragment A or B will render X redundant, even though it is not contained within either.
866866
// This algorithm will detect that and mark X for removal.
867867
//
868868
// To find the left neighbor, we need to find the fragment crossing the left boundary whose offset is the smallest.

tests/src/test_intrusive_rx.c

Lines changed: 202 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,27 @@ static udpard_fragment_t* fragment_at(udpard_tree_t* const root, uint32_t index)
2727
return NULL;
2828
}
2929

30-
/// Allocates the payload on the heap, emulating normal transfer reception.
31-
/// The payload shall not contain NUL characters.
32-
static rx_frame_base_t make_frame_base(const udpard_mem_resource_t mem, const size_t offset, const char* const payload)
30+
/// Allocates the payload on the heap, emulating normal frame reception.
31+
static rx_frame_base_t make_frame_base(const udpard_mem_resource_t mem,
32+
const size_t offset,
33+
const size_t size,
34+
const void* const payload)
3335
{
34-
const size_t size = (payload != NULL) ? (strlen(payload) + 1) : 0U;
35-
void* data = mem.alloc(mem.user, size);
36-
memcpy(data, payload, size);
36+
void* data = mem.alloc(mem.user, size);
37+
if (size > 0) {
38+
memcpy(data, payload, size);
39+
}
3740
return (rx_frame_base_t){ .offset = offset,
3841
.payload = { .data = data, .size = size },
3942
.origin = { .data = data, .size = size } };
4043
}
44+
/// The payload string cannot contain NUL characters.
45+
static rx_frame_base_t make_frame_base_str(const udpard_mem_resource_t mem,
46+
const size_t offset,
47+
const char* const payload)
48+
{
49+
return make_frame_base(mem, offset, (payload != NULL) ? (strlen(payload) + 1) : 0U, payload);
50+
}
4151

4252
static void test_rx_fragment_tree_update_a(void)
4353
{
@@ -59,7 +69,7 @@ static void test_rx_fragment_tree_update_a(void)
5969
res = rx_fragment_tree_update(&root, //
6070
mem_frag,
6171
del_payload,
62-
make_frame_base(mem_payload, 0, NULL),
72+
make_frame_base_str(mem_payload, 0, NULL),
6373
0,
6474
0,
6575
&cov);
@@ -101,7 +111,7 @@ static void test_rx_fragment_tree_update_a(void)
101111
res = rx_fragment_tree_update(&root, //
102112
mem_frag,
103113
del_payload,
104-
make_frame_base(mem_payload, 0, "abc"),
114+
make_frame_base_str(mem_payload, 0, "abc"),
105115
4,
106116
0,
107117
&cov);
@@ -143,7 +153,7 @@ static void test_rx_fragment_tree_update_a(void)
143153
res = rx_fragment_tree_update(&root, //
144154
mem_frag,
145155
del_payload,
146-
make_frame_base(mem_payload, 0, "abcdef"),
156+
make_frame_base_str(mem_payload, 0, "abcdef"),
147157
7,
148158
3,
149159
&cov);
@@ -186,7 +196,7 @@ static void test_rx_fragment_tree_update_a(void)
186196
res = rx_fragment_tree_update(&root, //
187197
mem_frag,
188198
del_payload,
189-
make_frame_base(mem_payload, 0, "abc"),
199+
make_frame_base_str(mem_payload, 0, "abc"),
190200
100,
191201
10,
192202
&cov);
@@ -198,7 +208,7 @@ static void test_rx_fragment_tree_update_a(void)
198208
res = rx_fragment_tree_update(&root, //
199209
mem_frag,
200210
del_payload,
201-
make_frame_base(mem_payload, 8, "xyz"),
211+
make_frame_base_str(mem_payload, 8, "xyz"),
202212
100,
203213
11,
204214
&cov);
@@ -210,7 +220,7 @@ static void test_rx_fragment_tree_update_a(void)
210220
res = rx_fragment_tree_update(&root, //
211221
mem_frag,
212222
del_payload,
213-
make_frame_base(mem_payload, 4, "def"),
223+
make_frame_base_str(mem_payload, 4, "def"),
214224
100,
215225
11,
216226
&cov);
@@ -246,6 +256,186 @@ static void test_rx_fragment_tree_update_a(void)
246256
TEST_ASSERT_EQUAL_size_t(3, alloc_frag.count_free);
247257
TEST_ASSERT_EQUAL_size_t(3, alloc_payload.count_free);
248258
}
259+
260+
instrumented_allocator_reset(&alloc_frag);
261+
instrumented_allocator_reset(&alloc_payload);
262+
263+
// Multi-frame reassembly test with defragmentation: "0123456789".
264+
{
265+
udpard_tree_t* root = NULL;
266+
size_t cov = 0;
267+
rx_fragment_tree_update_result_t res = rx_fragment_tree_not_done;
268+
// Add fragment.
269+
res = rx_fragment_tree_update(&root, //
270+
mem_frag,
271+
del_payload,
272+
make_frame_base(mem_payload, 0, 2, "01"),
273+
100,
274+
10,
275+
&cov);
276+
TEST_ASSERT_EQUAL(rx_fragment_tree_not_done, res);
277+
TEST_ASSERT_EQUAL_size_t(2, cov);
278+
TEST_ASSERT_NOT_NULL(root);
279+
TEST_ASSERT_EQUAL(1, tree_count(root));
280+
// Add fragment.
281+
res = rx_fragment_tree_update(&root, //
282+
mem_frag,
283+
del_payload,
284+
make_frame_base(mem_payload, 4, 2, "45"),
285+
100,
286+
10,
287+
&cov);
288+
TEST_ASSERT_EQUAL(rx_fragment_tree_not_done, res);
289+
TEST_ASSERT_EQUAL_size_t(2, cov); // not extended
290+
TEST_ASSERT_NOT_NULL(root);
291+
TEST_ASSERT_EQUAL(2, tree_count(root));
292+
// Add fragment.
293+
res = rx_fragment_tree_update(&root, //
294+
mem_frag,
295+
del_payload,
296+
make_frame_base(mem_payload, 3, 2, "34"),
297+
100,
298+
10,
299+
&cov);
300+
TEST_ASSERT_EQUAL(rx_fragment_tree_not_done, res);
301+
TEST_ASSERT_EQUAL_size_t(2, cov); // not extended
302+
TEST_ASSERT_NOT_NULL(root);
303+
TEST_ASSERT_EQUAL(3, tree_count(root));
304+
// Intermediate check on the current state of the tree so far.
305+
TEST_ASSERT_EQUAL_size_t(0, fragment_at(root, 0)->offset);
306+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 0)->view.size);
307+
TEST_ASSERT_EQUAL_STRING_LEN("01", fragment_at(root, 0)->view.data, 2);
308+
TEST_ASSERT_EQUAL_size_t(3, fragment_at(root, 1)->offset);
309+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 1)->view.size);
310+
TEST_ASSERT_EQUAL_STRING_LEN("34", fragment_at(root, 1)->view.data, 2);
311+
TEST_ASSERT_EQUAL_size_t(4, fragment_at(root, 2)->offset);
312+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 2)->view.size);
313+
TEST_ASSERT_EQUAL_STRING_LEN("45", fragment_at(root, 2)->view.data, 2);
314+
TEST_ASSERT_NULL(fragment_at(root, 3));
315+
TEST_ASSERT_EQUAL_size_t(3, alloc_frag.allocated_fragments);
316+
TEST_ASSERT_EQUAL_size_t(3, alloc_payload.allocated_fragments);
317+
TEST_ASSERT_EQUAL_size_t(3, alloc_frag.count_alloc);
318+
TEST_ASSERT_EQUAL_size_t(3, alloc_payload.count_alloc);
319+
TEST_ASSERT_EQUAL_size_t(0, alloc_frag.count_free);
320+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.count_free);
321+
// Add fragment. BRIDGE THE LEFT GAP, EVICT `34` FRAGMENT AS REDUNDANT.
322+
res = rx_fragment_tree_update(&root, //
323+
mem_frag,
324+
del_payload,
325+
make_frame_base(mem_payload, 2, 2, "23"),
326+
100,
327+
10,
328+
&cov);
329+
TEST_ASSERT_EQUAL(rx_fragment_tree_not_done, res);
330+
TEST_ASSERT_EQUAL_size_t(6, cov); // extended!
331+
TEST_ASSERT_NOT_NULL(root);
332+
TEST_ASSERT_EQUAL(3, tree_count(root));
333+
// Check the updated tree state after the eviction. Fragment `34` should be gone.
334+
TEST_ASSERT_EQUAL_size_t(0, fragment_at(root, 0)->offset);
335+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 0)->view.size);
336+
TEST_ASSERT_EQUAL_STRING_LEN("01", fragment_at(root, 0)->view.data, 2);
337+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 1)->offset);
338+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 1)->view.size);
339+
TEST_ASSERT_EQUAL_STRING_LEN("23", fragment_at(root, 1)->view.data, 2);
340+
TEST_ASSERT_EQUAL_size_t(4, fragment_at(root, 2)->offset);
341+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 2)->view.size);
342+
TEST_ASSERT_EQUAL_STRING_LEN("45", fragment_at(root, 2)->view.data, 2);
343+
TEST_ASSERT_NULL(fragment_at(root, 3));
344+
TEST_ASSERT_EQUAL_size_t(3, alloc_frag.allocated_fragments);
345+
TEST_ASSERT_EQUAL_size_t(3, alloc_payload.allocated_fragments);
346+
TEST_ASSERT_EQUAL_size_t(4, alloc_frag.count_alloc);
347+
TEST_ASSERT_EQUAL_size_t(4, alloc_payload.count_alloc);
348+
TEST_ASSERT_EQUAL_size_t(1, alloc_frag.count_free);
349+
TEST_ASSERT_EQUAL_size_t(1, alloc_payload.count_free);
350+
// Add a fully-contained (redundant) fragment. Should be discarded.
351+
res = rx_fragment_tree_update(&root, //
352+
mem_frag,
353+
del_payload,
354+
make_frame_base(mem_payload, 1, 2, ":3"),
355+
100,
356+
10,
357+
&cov);
358+
TEST_ASSERT_EQUAL(rx_fragment_tree_not_done, res);
359+
TEST_ASSERT_EQUAL_size_t(6, cov); // no new information is added
360+
TEST_ASSERT_NOT_NULL(root);
361+
TEST_ASSERT_EQUAL(3, tree_count(root)); // no new frames added
362+
TEST_ASSERT_EQUAL_size_t(3, alloc_frag.allocated_fragments); // no new allocations
363+
TEST_ASSERT_EQUAL_size_t(3, alloc_payload.allocated_fragments);
364+
TEST_ASSERT_EQUAL_size_t(4, alloc_frag.count_alloc);
365+
TEST_ASSERT_EQUAL_size_t(5, alloc_payload.count_alloc); // the payload was briefly allocated and discarded
366+
TEST_ASSERT_EQUAL_size_t(1, alloc_frag.count_free);
367+
TEST_ASSERT_EQUAL_size_t(2, alloc_payload.count_free); // yeah, discarded
368+
// Add fragment. Slight overlap on the right, candidate for eviction in the future.
369+
res = rx_fragment_tree_update(&root, //
370+
mem_frag,
371+
del_payload,
372+
make_frame_base(mem_payload, 5, 2, "56"),
373+
100,
374+
10,
375+
&cov);
376+
TEST_ASSERT_EQUAL(rx_fragment_tree_not_done, res);
377+
TEST_ASSERT_EQUAL_size_t(7, cov); // extended by 1 byte
378+
TEST_ASSERT_NOT_NULL(root);
379+
TEST_ASSERT_EQUAL(4, tree_count(root));
380+
// Check the updated tree state.
381+
TEST_ASSERT_EQUAL_size_t(0, fragment_at(root, 0)->offset);
382+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 0)->view.size);
383+
TEST_ASSERT_EQUAL_STRING_LEN("01", fragment_at(root, 0)->view.data, 2);
384+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 1)->offset);
385+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 1)->view.size);
386+
TEST_ASSERT_EQUAL_STRING_LEN("23", fragment_at(root, 1)->view.data, 2);
387+
TEST_ASSERT_EQUAL_size_t(4, fragment_at(root, 2)->offset);
388+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 2)->view.size);
389+
TEST_ASSERT_EQUAL_STRING_LEN("45", fragment_at(root, 2)->view.data, 2);
390+
TEST_ASSERT_EQUAL_size_t(5, fragment_at(root, 3)->offset);
391+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 3)->view.size);
392+
TEST_ASSERT_EQUAL_STRING_LEN("56", fragment_at(root, 3)->view.data, 2);
393+
TEST_ASSERT_NULL(fragment_at(root, 4));
394+
TEST_ASSERT_EQUAL_size_t(4, alloc_frag.allocated_fragments);
395+
TEST_ASSERT_EQUAL_size_t(4, alloc_payload.allocated_fragments);
396+
TEST_ASSERT_EQUAL_size_t(5, alloc_frag.count_alloc);
397+
TEST_ASSERT_EQUAL_size_t(6, alloc_payload.count_alloc);
398+
TEST_ASSERT_EQUAL_size_t(1, alloc_frag.count_free);
399+
TEST_ASSERT_EQUAL_size_t(2, alloc_payload.count_free);
400+
// Add fragment. Completes the transfer and evicts redundant `45` and `56` fragments.
401+
res = rx_fragment_tree_update(&root, //
402+
mem_frag,
403+
del_payload,
404+
make_frame_base(mem_payload, 4, 8, "456789--"),
405+
100,
406+
10,
407+
&cov);
408+
TEST_ASSERT_EQUAL(rx_fragment_tree_done, res);
409+
TEST_ASSERT_EQUAL_size_t(12, cov); // extended all the way, beyond the extent.
410+
TEST_ASSERT_NOT_NULL(root);
411+
TEST_ASSERT_EQUAL(3, tree_count(root)); // the tree shrunk due to evictions
412+
// Check the updated tree state.
413+
TEST_ASSERT_EQUAL_size_t(0, fragment_at(root, 0)->offset);
414+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 0)->view.size);
415+
TEST_ASSERT_EQUAL_STRING_LEN("01", fragment_at(root, 0)->view.data, 2);
416+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 1)->offset);
417+
TEST_ASSERT_EQUAL_size_t(2, fragment_at(root, 1)->view.size);
418+
TEST_ASSERT_EQUAL_STRING_LEN("23", fragment_at(root, 1)->view.data, 2);
419+
TEST_ASSERT_EQUAL_size_t(4, fragment_at(root, 2)->offset);
420+
TEST_ASSERT_EQUAL_size_t(8, fragment_at(root, 2)->view.size);
421+
TEST_ASSERT_EQUAL_STRING_LEN("456789--", fragment_at(root, 2)->view.data, 8);
422+
TEST_ASSERT_NULL(fragment_at(root, 3));
423+
TEST_ASSERT_EQUAL_size_t(3, alloc_frag.allocated_fragments);
424+
TEST_ASSERT_EQUAL_size_t(3, alloc_payload.allocated_fragments);
425+
TEST_ASSERT_EQUAL_size_t(6, alloc_frag.count_alloc);
426+
TEST_ASSERT_EQUAL_size_t(7, alloc_payload.count_alloc);
427+
TEST_ASSERT_EQUAL_size_t(3, alloc_frag.count_free);
428+
TEST_ASSERT_EQUAL_size_t(4, alloc_payload.count_free);
429+
// Free the tree (as in freedom). The free tree is free to manifest its own destiny.
430+
udpard_fragment_free_all((udpard_fragment_t*)root, mem_frag);
431+
// Check the heap.
432+
TEST_ASSERT_EQUAL_size_t(0, alloc_frag.allocated_fragments);
433+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.allocated_fragments);
434+
TEST_ASSERT_EQUAL_size_t(6, alloc_frag.count_alloc);
435+
TEST_ASSERT_EQUAL_size_t(7, alloc_payload.count_alloc);
436+
TEST_ASSERT_EQUAL_size_t(6, alloc_frag.count_free);
437+
TEST_ASSERT_EQUAL_size_t(7, alloc_payload.count_free);
438+
}
249439
}
250440

251441
static void test_rx_transfer_id_forward_distance(void)

0 commit comments

Comments
 (0)