Skip to content

Commit e1d6b97

Browse files
add exhaustive fragmentation test
1 parent eb1d592 commit e1d6b97

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed

tests/src/test_intrusive_rx.c

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,198 @@ static void test_rx_fragment_tree_update_a(void)
743743
instrumented_allocator_reset(&alloc_payload);
744744
}
745745

746+
/// Exhaustive test for rx_fragment_tree_update with random fragmentation patterns.
747+
/// Tests a fixed payload split into every possible non-empty substring,
748+
/// fed in random order with possible duplicates, and verifies correct completion detection.
749+
static void test_rx_fragment_tree_update_exhaustive(void)
750+
{
751+
instrumented_allocator_t alloc_frag = { 0 };
752+
instrumented_allocator_new(&alloc_frag);
753+
const udpard_mem_resource_t mem_frag = instrumented_allocator_make_resource(&alloc_frag);
754+
755+
instrumented_allocator_t alloc_payload = { 0 };
756+
instrumented_allocator_new(&alloc_payload);
757+
const udpard_mem_resource_t mem_payload = instrumented_allocator_make_resource(&alloc_payload);
758+
const udpard_mem_deleter_t del_payload = instrumented_allocator_make_deleter(&alloc_payload);
759+
760+
const char payload[] = "0123456789";
761+
const size_t payload_length = strlen(payload);
762+
763+
// Generate all possible non-empty substrings (offset, length pairs).
764+
// For a string of length N, there are N*(N+1)/2 possible substrings.
765+
typedef struct
766+
{
767+
size_t offset;
768+
size_t length;
769+
} substring_t;
770+
771+
const size_t max_substrings = (payload_length * (payload_length + 1)) / 2;
772+
substring_t substrings[max_substrings];
773+
size_t substring_count = 0;
774+
775+
for (size_t offset = 0; offset < payload_length; offset++) {
776+
for (size_t length = 1; length <= (payload_length - offset); length++) {
777+
substrings[substring_count].offset = offset;
778+
substrings[substring_count].length = length;
779+
substring_count++;
780+
}
781+
}
782+
TEST_ASSERT_EQUAL_size_t(max_substrings, substring_count);
783+
784+
// Run multiple randomized test iterations to explore different orderings.
785+
// We use fewer iterations to keep test time reasonable (~10 seconds).
786+
const size_t num_iterations = 10000;
787+
788+
for (size_t iteration = 0; iteration < num_iterations; iteration++) {
789+
udpard_tree_t* root = NULL;
790+
size_t cov = 0;
791+
792+
// Create a randomized schedule of fragments to feed.
793+
// We'll randomly select which substrings to use and in what order.
794+
// Some may be duplicated, some may be omitted initially.
795+
796+
// Track which bytes have been covered by submitted fragments.
797+
bool byte_covered[10] = { false };
798+
bool transfer_complete = false;
799+
800+
// Shuffle the substring indices to get a random order.
801+
size_t schedule[substring_count];
802+
for (size_t i = 0; i < substring_count; i++) {
803+
schedule[i] = i;
804+
}
805+
806+
// Fisher-Yates shuffle
807+
for (size_t i = substring_count - 1; i > 0; i--) {
808+
const size_t j = (size_t)(rand() % (int)(i + 1));
809+
const size_t tmp = schedule[i];
810+
schedule[i] = schedule[j];
811+
schedule[j] = tmp;
812+
}
813+
814+
// Feed fragments in the shuffled order.
815+
// We stop after we've seen every byte at least once.
816+
for (size_t sched_idx = 0; sched_idx < substring_count; sched_idx++) {
817+
const substring_t sub = substrings[schedule[sched_idx]];
818+
819+
// Allocate and copy the substring payload.
820+
char* const frag_data = mem_payload.alloc(mem_payload.user, sub.length);
821+
memcpy(frag_data, payload + sub.offset, sub.length);
822+
823+
const rx_frame_base_t frame = { .offset = sub.offset,
824+
.payload = { .data = frag_data, .size = sub.length },
825+
.origin = { .data = frag_data, .size = sub.length } };
826+
827+
const rx_fragment_tree_update_result_t res =
828+
rx_fragment_tree_update(&root, mem_frag, del_payload, frame, payload_length, payload_length, &cov);
829+
830+
// Update our tracking of covered bytes.
831+
for (size_t i = 0; i < sub.length; i++) {
832+
byte_covered[sub.offset + i] = true;
833+
}
834+
835+
// Check if all bytes are covered.
836+
bool all_covered = true;
837+
for (size_t i = 0; i < payload_length; i++) {
838+
if (!byte_covered[i]) {
839+
all_covered = false;
840+
break;
841+
}
842+
}
843+
844+
if (all_covered) {
845+
// The function should report completion exactly when all bytes are covered.
846+
TEST_ASSERT_EQUAL(rx_fragment_tree_done, res);
847+
transfer_complete = true;
848+
break; // Stop feeding more fragments once complete.
849+
}
850+
// The function should NOT report completion if not all bytes are covered.
851+
TEST_ASSERT_EQUAL(rx_fragment_tree_not_done, res);
852+
}
853+
854+
// Verify that we did achieve completion (our shuffled order should have covered everything).
855+
TEST_ASSERT_TRUE(transfer_complete);
856+
857+
// Verify coverage matches the payload length.
858+
TEST_ASSERT_EQUAL_size_t(payload_length, cov);
859+
860+
// Cleanup for this iteration.
861+
udpard_fragment_free_all((udpard_fragment_t*)root, mem_frag);
862+
TEST_ASSERT_EQUAL_size_t(0, alloc_frag.allocated_fragments);
863+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.allocated_fragments);
864+
}
865+
TEST_ASSERT_EQUAL_size_t(0, alloc_frag.allocated_fragments);
866+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.allocated_fragments);
867+
868+
// Test with duplicates: feed the same fragments multiple times.
869+
for (size_t iteration = 0; iteration < 100; iteration++) {
870+
udpard_tree_t* root = NULL;
871+
size_t cov = 0;
872+
873+
bool byte_covered[10] = { false };
874+
bool transfer_complete = false;
875+
876+
// Create a schedule with duplicates.
877+
const size_t schedule_length = substring_count * 3; // 3x duplication factor
878+
size_t schedule[schedule_length];
879+
for (size_t i = 0; i < schedule_length; i++) {
880+
schedule[i] = (size_t)(rand() % (int)substring_count);
881+
}
882+
883+
// Feed fragments with duplicates.
884+
for (size_t sched_idx = 0; sched_idx < schedule_length; sched_idx++) {
885+
const substring_t sub = substrings[schedule[sched_idx]];
886+
887+
char* const frag_data = mem_payload.alloc(mem_payload.user, sub.length);
888+
memcpy(frag_data, payload + sub.offset, sub.length);
889+
890+
const rx_frame_base_t frame = { .offset = sub.offset,
891+
.payload = { .data = frag_data, .size = sub.length },
892+
.origin = { .data = frag_data, .size = sub.length } };
893+
894+
const rx_fragment_tree_update_result_t res =
895+
rx_fragment_tree_update(&root, mem_frag, del_payload, frame, payload_length, payload_length, &cov);
896+
897+
// Update tracking.
898+
for (size_t i = 0; i < sub.length; i++) {
899+
byte_covered[sub.offset + i] = true;
900+
}
901+
902+
// Check completion.
903+
bool all_covered = true;
904+
for (size_t i = 0; i < payload_length; i++) {
905+
if (!byte_covered[i]) {
906+
all_covered = false;
907+
break;
908+
}
909+
}
910+
911+
if (all_covered && !transfer_complete) {
912+
// The first time we achieve full coverage, the function should report done.
913+
TEST_ASSERT_EQUAL(rx_fragment_tree_done, res);
914+
transfer_complete = true;
915+
// Continue feeding duplicates to ensure no issues.
916+
} else if (transfer_complete) {
917+
// Once complete, subsequent fragments may return either done or not_done,
918+
// depending on whether they are accepted or rejected by the heuristic.
919+
// The important thing is that coverage remains at payload_length.
920+
TEST_ASSERT_NOT_EQUAL(rx_fragment_tree_oom, res);
921+
TEST_ASSERT_EQUAL_size_t(payload_length, cov);
922+
} else {
923+
TEST_ASSERT_EQUAL(rx_fragment_tree_not_done, res);
924+
}
925+
}
926+
927+
TEST_ASSERT_TRUE(transfer_complete);
928+
TEST_ASSERT_EQUAL_size_t(payload_length, cov);
929+
930+
udpard_fragment_free_all((udpard_fragment_t*)root, mem_frag);
931+
TEST_ASSERT_EQUAL_size_t(0, alloc_frag.allocated_fragments);
932+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.allocated_fragments);
933+
}
934+
TEST_ASSERT_EQUAL_size_t(0, alloc_frag.allocated_fragments);
935+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.allocated_fragments);
936+
}
937+
746938
static void test_rx_transfer_id_forward_distance(void)
747939
{
748940
// Test 1: Same value (distance is 0)
@@ -1026,6 +1218,7 @@ int main(void)
10261218
{
10271219
UNITY_BEGIN();
10281220
RUN_TEST(test_rx_fragment_tree_update_a);
1221+
RUN_TEST(test_rx_fragment_tree_update_exhaustive);
10291222
RUN_TEST(test_rx_transfer_id_forward_distance);
10301223
RUN_TEST(test_rx_transfer_id_window_slide);
10311224
RUN_TEST(test_rx_transfer_id_window_manip);

0 commit comments

Comments
 (0)