@@ -1142,6 +1142,85 @@ static void test_tx_spool_deduplication(void)
11421142 TEST_ASSERT_EQUAL_size_t (0 , alloc_b .allocated_fragments );
11431143}
11441144
1145+ // Verifies that eject callbacks are ONLY invoked from udpard_tx_poll(), never from push functions.
1146+ static void test_tx_eject_only_from_poll (void )
1147+ {
1148+ instrumented_allocator_t alloc = { 0 };
1149+ instrumented_allocator_new (& alloc );
1150+ udpard_tx_mem_resources_t mem = { .transfer = instrumented_allocator_make_resource (& alloc ) };
1151+ for (size_t i = 0 ; i < UDPARD_IFACE_COUNT_MAX ; i ++ ) {
1152+ mem .payload [i ] = instrumented_allocator_make_resource (& alloc );
1153+ }
1154+
1155+ udpard_tx_t tx = { 0 };
1156+ eject_state_t eject = { .count = 0 , .allow = true };
1157+ udpard_tx_vtable_t vt = { .eject_subject = eject_subject_with_flag , .eject_p2p = eject_p2p_with_flag };
1158+ TEST_ASSERT_TRUE (udpard_tx_new (& tx , 60U , 1U , 16U , mem , & vt ));
1159+ tx .user = & eject ;
1160+
1161+ const uint16_t iface_bitmap_1 = (1U << 0U );
1162+
1163+ // Push a subject transfer; eject must NOT be called.
1164+ eject .count = 0 ;
1165+ TEST_ASSERT_TRUE (udpard_tx_push (
1166+ & tx , 0 , 1000 , iface_bitmap_1 , udpard_prio_fast , 100 , 1 , make_scattered (NULL , 0 ), NULL , UDPARD_USER_CONTEXT_NULL ));
1167+ TEST_ASSERT_EQUAL_size_t (0 , eject .count ); // eject NOT called from push
1168+
1169+ // Push a P2P transfer; eject must NOT be called.
1170+ const udpard_remote_t remote = { .uid = 999 , .endpoints = { make_ep (10 ) } };
1171+ TEST_ASSERT_TRUE (udpard_tx_push_p2p (& tx ,
1172+ 0 ,
1173+ 1000 ,
1174+ udpard_prio_fast ,
1175+ 0xABCD ,
1176+ 42 ,
1177+ remote ,
1178+ make_scattered (NULL , 0 ),
1179+ NULL ,
1180+ UDPARD_USER_CONTEXT_NULL ,
1181+ NULL ));
1182+ TEST_ASSERT_EQUAL_size_t (0 , eject .count ); // eject NOT called from push_p2p
1183+
1184+ // Now poll; eject MUST be called.
1185+ udpard_tx_poll (& tx , 0 , UDPARD_IFACE_BITMAP_ALL );
1186+ TEST_ASSERT_GREATER_THAN_size_t (0 , eject .count ); // eject called from poll
1187+
1188+ // Push more transfers while frames are pending; eject still must NOT be called.
1189+ const size_t eject_count_before = eject .count ;
1190+ eject .allow = false; // block ejection to keep frames pending
1191+ TEST_ASSERT_TRUE (udpard_tx_push (& tx ,
1192+ 0 ,
1193+ 1000 ,
1194+ iface_bitmap_1 ,
1195+ udpard_prio_nominal ,
1196+ 200 ,
1197+ 2 ,
1198+ make_scattered (NULL , 0 ),
1199+ NULL ,
1200+ UDPARD_USER_CONTEXT_NULL ));
1201+ TEST_ASSERT_EQUAL_size_t (eject_count_before , eject .count ); // eject NOT called from push
1202+
1203+ TEST_ASSERT_TRUE (udpard_tx_push_p2p (& tx ,
1204+ 0 ,
1205+ 1000 ,
1206+ udpard_prio_nominal ,
1207+ 0xBEEF ,
1208+ 99 ,
1209+ remote ,
1210+ make_scattered (NULL , 0 ),
1211+ NULL ,
1212+ UDPARD_USER_CONTEXT_NULL ,
1213+ NULL ));
1214+ TEST_ASSERT_EQUAL_size_t (eject_count_before , eject .count ); // eject NOT called from push_p2p
1215+
1216+ // Poll again; eject called again (but rejected by callback).
1217+ udpard_tx_poll (& tx , 0 , UDPARD_IFACE_BITMAP_ALL );
1218+ TEST_ASSERT_GREATER_THAN_size_t (eject_count_before , eject .count ); // eject called from poll
1219+
1220+ udpard_tx_free (& tx );
1221+ instrumented_allocator_reset (& alloc );
1222+ }
1223+
11451224void setUp (void ) {}
11461225
11471226void tearDown (void ) {}
@@ -1161,6 +1240,7 @@ int main(void)
11611240 RUN_TEST (test_tx_cancel_p2p );
11621241 RUN_TEST (test_tx_cancel_all );
11631242 RUN_TEST (test_tx_spool_deduplication );
1243+ RUN_TEST (test_tx_eject_only_from_poll );
11641244 RUN_TEST (test_tx_ack_and_scheduler );
11651245 return UNITY_END ();
11661246}
0 commit comments