Skip to content

Commit 4525030

Browse files
net: framed: proptest generators for multibuf cancel safe testing (#958)
Summary: Pull Request resolved: #958 introduce proptest generators for multipart payloads. `multipart_part` yields a single part (including empty), `multipart_message` covers unipart and `multipart` (1–3 extra parts), `multipart_message_only` forces multipart. add sanity property tests to check part count bounds, `frame_len` accounting, and round-trip through `from_framed`. establishes the generators for use in the upcoming cancel-safety property test. Reviewed By: mariusae Differential Revision: D80739232 fbshipit-source-id: 0caaa9ef23d5e90a4a3f7f9825f02c2dc7a84eb6
1 parent 5dad2de commit 4525030

File tree

1 file changed

+82
-0
lines changed

1 file changed

+82
-0
lines changed

hyperactor/src/channel/net/framed.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ mod test_support {
234234
use std::task::Waker;
235235

236236
use proptest::prelude::*;
237+
use serde_multipart::Message;
238+
use serde_multipart::Part;
237239

238240
use super::*;
239241

@@ -608,6 +610,45 @@ mod test_support {
608610
// length: 1..=8, step: 1..=8 (no zeros)
609611
prop::collection::vec(1..=8usize, 1..=8)
610612
}
613+
614+
/// Generate a single multipart `Part` with up to `max_len` bytes.
615+
/// Includes empty parts to exercise edge cases and boundary
616+
/// slicing.
617+
pub fn multipart_part(max_len: usize) -> impl Strategy<Value = Part> {
618+
proptest::collection::vec(any::<u8>(), 0..=max_len).prop_map(Part::from)
619+
}
620+
621+
/// Generate a `Message` that is either unipart or true multipart
622+
/// (body + 0..=3 additional parts). Kept small for fast tests.
623+
pub fn multipart_message() -> impl Strategy<Value = Message> {
624+
let max_parts = 4usize; // total parts = 1..=4
625+
let max_len = 64usize; // bytes per part
626+
627+
prop_oneof![
628+
// Unipart (compat path): body only
629+
multipart_part(max_len).prop_map(|body| Message::from_body_and_parts(body, vec![])),
630+
// Multipart: body + 1..=3 extra parts
631+
(
632+
multipart_part(max_len),
633+
proptest::collection::vec(multipart_part(max_len), 1..=max_parts - 1)
634+
)
635+
.prop_map(|(body, parts)| Message::from_body_and_parts(body, parts)),
636+
]
637+
}
638+
639+
/// Generate a `Message` that is *strictly* multipart (body +
640+
/// 1..=3 additional parts). Use this when you want to force the
641+
/// vectored path every time.
642+
pub fn multipart_message_only() -> impl Strategy<Value = Message> {
643+
let max_parts = 4usize; // total parts = 2..=4
644+
let max_len = 64usize;
645+
646+
(
647+
multipart_part(max_len),
648+
proptest::collection::vec(multipart_part(max_len), 1..=max_parts - 1),
649+
)
650+
.prop_map(|(body, parts)| Message::from_body_and_parts(body, parts))
651+
}
611652
}
612653

613654
#[cfg(test)]
@@ -849,6 +890,47 @@ mod property_tests {
849890
}
850891
}
851892

893+
proptest! {
894+
// Sanity: multipart_message() yields either unipart or 1..=3
895+
// extra parts, frames to the advertised length, and
896+
// round-trips via from_framed().
897+
#[test]
898+
fn test_multipart_message_shape(msg in test_support::multipart_message()) {
899+
// Parts count bounds (unipart allowed)
900+
prop_assert!(msg.num_parts() <= 3);
901+
902+
// `frame_len` matches actual framed length
903+
let mut framed = msg.clone().framed();
904+
let framed_bytes = framed.copy_to_bytes(framed.remaining());
905+
prop_assert_eq!(framed_bytes.len(), msg.frame_len());
906+
907+
// round-trip `framed` → `Message` → `framed` produces
908+
// identical bytes
909+
let rt = serde_multipart::Message::from_framed(framed_bytes.clone()).unwrap();
910+
let mut rt_framed = rt.framed();
911+
let rt_bytes = rt_framed.copy_to_bytes(rt_framed.remaining());
912+
prop_assert_eq!(rt_bytes, framed_bytes);
913+
}
914+
915+
// Sanity: multipart_message_only() always yields *strictly*
916+
// multipart (≥1 extra part), and also round-trips and
917+
// length-checks as above.
918+
#[test]
919+
fn test_multipart_message_only_shape(msg in test_support::multipart_message_only()) {
920+
// Strictly multipart.
921+
prop_assert!(msg.num_parts() >= 1 && msg.num_parts() <= 3);
922+
923+
let mut framed = msg.clone().framed();
924+
let framed_bytes = framed.copy_to_bytes(framed.remaining());
925+
prop_assert_eq!(framed_bytes.len(), msg.frame_len());
926+
927+
let rt = serde_multipart::Message::from_framed(framed_bytes.clone()).unwrap();
928+
let mut rt_framed = rt.framed();
929+
let rt_bytes = rt_framed.copy_to_bytes(rt_framed.remaining());
930+
prop_assert_eq!(rt_bytes, framed_bytes);
931+
}
932+
}
933+
852934
// Theorem: `FrameWrite::send` is cancel-safe.
853935
//
854936
// Matches the cancel-safety contract from `test_utils::cancel_safe`:

0 commit comments

Comments
 (0)