diff --git a/Cargo.lock b/Cargo.lock index 2b4c8389..5ab7819c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2403,6 +2403,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -2893,6 +2899,7 @@ dependencies = [ "localtime", "log", "nonempty 0.9.0", + "paste", "qcheck", "qcheck-macros", "radicle", diff --git a/crates/radicle-protocol/Cargo.toml b/crates/radicle-protocol/Cargo.toml index 5c9a4d62..9ed5c663 100644 --- a/crates/radicle-protocol/Cargo.toml +++ b/crates/radicle-protocol/Cargo.toml @@ -31,6 +31,7 @@ serde_json = { workspace = true, features = ["preserve_order"] } thiserror = { workspace = true } [dev-dependencies] +paste = "1.0.15" qcheck = { workspace = true } qcheck-macros = { workspace = true } radicle = { workspace = true, features = ["test"] } diff --git a/crates/radicle-protocol/src/wire.rs b/crates/radicle-protocol/src/wire.rs index 3c1b8baa..577218e8 100644 --- a/crates/radicle-protocol/src/wire.rs +++ b/crates/radicle-protocol/src/wire.rs @@ -560,9 +560,37 @@ impl Decode for Timestamp { } } +#[cfg(test)] +fn roundtrip(value: T) +where + T: Encode + Decode + PartialEq + Debug, +{ + let encoded = value.encode_to_vec(); + assert_eq!(T::decode_exact(&encoded).expect("roundtrip"), value); +} + +#[cfg(test)] +#[macro_export] +macro_rules! prop_roundtrip { + ($t:ty, $name:tt) => { + paste::paste! { + #[quickcheck] + fn [< prop_roundtrip_ $name:lower >](v: $t) { + $crate::wire::roundtrip(v); + } + } + }; + ($t:ty) => { + paste::paste! { + prop_roundtrip!($t, [< $t >]); + } + }; +} + #[cfg(test)] mod tests { use super::*; + use qcheck; use qcheck_macros::quickcheck; @@ -570,101 +598,36 @@ mod tests { use radicle::crypto::Unverified; use radicle::storage::refs::SignedRefs; - #[quickcheck] - fn prop_u8(input: u8) { - assert_eq!(u8::decode_exact(&input.encode_to_vec()).unwrap(), input); - } - - #[quickcheck] - fn prop_u16(input: u16) { - assert_eq!(u16::decode_exact(&input.encode_to_vec()).unwrap(), input); - } - - #[quickcheck] - fn prop_u32(input: u32) { - assert_eq!(u32::decode_exact(&input.encode_to_vec()).unwrap(), input); - } - - #[quickcheck] - fn prop_u64(input: u64) { - assert_eq!(u64::decode_exact(&input.encode_to_vec()).unwrap(), input); - } + prop_roundtrip!(u16); + prop_roundtrip!(u32); + prop_roundtrip!(u64); + prop_roundtrip!(BoundedVec, vec); + prop_roundtrip!(PublicKey); + prop_roundtrip!(filter::Filter, filter); + prop_roundtrip!(RepoId); + prop_roundtrip!(Refs); + prop_roundtrip!((String, String), tuple); + prop_roundtrip!(SignedRefs, signed_refs); #[quickcheck] fn prop_string(input: String) -> qcheck::TestResult { if input.len() > u8::MAX as usize { return qcheck::TestResult::discard(); } - assert_eq!(String::decode_exact(&input.encode_to_vec()).unwrap(), input); + + roundtrip(input); qcheck::TestResult::passed() } - #[quickcheck] - fn prop_vec(input: BoundedVec) { - assert_eq!( - BoundedVec::::decode_exact(&input.encode_to_vec()).unwrap(), - input - ); - } - - #[quickcheck] - fn prop_pubkey(input: PublicKey) { - assert_eq!( - PublicKey::decode_exact(&input.encode_to_vec()).unwrap(), - input - ); - } - - #[quickcheck] - fn prop_filter(input: filter::Filter) { - assert_eq!( - filter::Filter::decode_exact(&input.encode_to_vec()).unwrap(), - input - ); - } - - #[quickcheck] - fn prop_id(input: RepoId) { - assert_eq!(RepoId::decode_exact(&input.encode_to_vec()).unwrap(), input); - } - - #[quickcheck] - fn prop_refs(input: Refs) { - assert_eq!(Refs::decode_exact(&input.encode_to_vec()).unwrap(), input); - } - - #[quickcheck] - fn prop_tuple(input: (String, String)) { - assert_eq!( - <(String, String)>::decode_exact(&input.encode_to_vec()).unwrap(), - input - ); - } - #[quickcheck] fn prop_signature(input: [u8; 64]) { - let signature = Signature::from(input); - - assert_eq!( - Signature::decode_exact(&signature.encode_to_vec()).unwrap(), - signature - ); + roundtrip(Signature::from(input)); } #[quickcheck] fn prop_oid(input: [u8; 20]) { - let oid = git::Oid::try_from(input.as_slice()).unwrap(); - - assert_eq!(git::Oid::decode_exact(&oid.encode_to_vec()).unwrap(), oid); - } - - #[quickcheck] - fn prop_signed_refs(input: SignedRefs) { - assert_eq!( - SignedRefs::::decode_exact(&input.encode_to_vec()).unwrap(), - input - ); + roundtrip(git::Oid::try_from(input.as_slice()).unwrap()); } #[test] diff --git a/crates/radicle-protocol/src/wire/message.rs b/crates/radicle-protocol/src/wire/message.rs index 6a837b79..7d3bb985 100644 --- a/crates/radicle-protocol/src/wire/message.rs +++ b/crates/radicle-protocol/src/wire/message.rs @@ -425,15 +425,20 @@ impl wire::Decode for ZeroBytes { #[cfg(test)] mod tests { - use super::*; use qcheck_macros::quickcheck; use radicle::node::device::Device; use radicle::node::UserAgent; use radicle::storage::refs::RefsAt; + use radicle::test::arbitrary; use crate::deserializer::Deserializer; - use crate::wire::{self, Decode, Encode}; - use radicle::test::arbitrary; + use crate::prop_roundtrip; + use crate::wire::{roundtrip, Encode as _}; + + use super::*; + + prop_roundtrip!(Address); + prop_roundtrip!(Message); #[test] fn test_refs_ann_max_size() { @@ -520,14 +525,6 @@ mod tests { .encode_to_vec(); } - #[quickcheck] - fn prop_message_encode_decode(message: Message) { - let encoded = message.encode_to_vec(); - let decoded = Message::decode_exact(&encoded).unwrap(); - - assert_eq!(message, decoded); - } - #[test] fn prop_message_decoder() { fn property(items: Vec) { @@ -546,28 +543,14 @@ mod tests { .quickcheck(property as fn(items: Vec)); } - #[test] - fn prop_zero_bytes_encode_decode() { - fn property(zeroes: wire::Size) { - if zeroes > Ping::MAX_PING_ZEROES { - return; - } - - let zeroes = ZeroBytes::new(zeroes); - - assert_eq!( - ZeroBytes::decode_exact(&zeroes.encode_to_vec()).unwrap(), - zeroes - ); + #[quickcheck] + fn prop_zero_bytes_encode_decode(zeroes: wire::Size) -> qcheck::TestResult { + if zeroes > Ping::MAX_PING_ZEROES { + return qcheck::TestResult::discard(); } - qcheck::QuickCheck::new() - .gen(qcheck::Gen::new(16)) - .quickcheck(property as fn(zeroes: wire::Size)); - } + roundtrip(ZeroBytes::new(zeroes)); - #[quickcheck] - fn prop_addr(addr: Address) { - assert_eq!(Address::decode_exact(&addr.encode_to_vec()).unwrap(), addr); + qcheck::TestResult::passed() } } diff --git a/crates/radicle-protocol/src/wire/varint.rs b/crates/radicle-protocol/src/wire/varint.rs index 1e1694dd..325eace4 100644 --- a/crates/radicle-protocol/src/wire/varint.rs +++ b/crates/radicle-protocol/src/wire/varint.rs @@ -175,9 +175,14 @@ pub mod payload { #[cfg(test)] mod test { - use super::*; use qcheck_macros::quickcheck; + use crate::prop_roundtrip; + + use super::*; + + prop_roundtrip!(VarInt); + impl qcheck::Arbitrary for VarInt { fn arbitrary(g: &mut qcheck::Gen) -> Self { let a = u16::arbitrary(g) as u64; @@ -211,16 +216,8 @@ mod test { } } - #[quickcheck] - fn prop_encode_decode(input: VarInt) { - let encoded = input.encode_to_vec(); - let decoded: VarInt = VarInt::decode_exact(&encoded).unwrap(); - - assert_eq!(decoded, input); - } - #[test] - #[should_panic] + #[should_panic(expected = "overflow")] fn test_encode_overflow() { VarInt(u64::MAX).encode_to_vec(); }