Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
protocol/wire/test: Define `fn roundtrip` and macro
Merged lorenz opened 8 months ago

In many tests we assert that encoding and decoding gives the same object. Let’s call that a “roundrip”, and also provide a proc macro to generate such tests.

5 files changed +71 -120 bbd1e2cf ded0d19d
modified Cargo.lock
@@ -2404,6 +2404,12 @@ dependencies = [
]

[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2893,6 +2899,7 @@ dependencies = [
 "localtime",
 "log",
 "nonempty 0.9.0",
+
 "paste",
 "qcheck",
 "qcheck-macros",
 "radicle",
modified 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"] }
modified crates/radicle-protocol/src/wire.rs
@@ -561,8 +561,36 @@ impl Decode for Timestamp {
}

#[cfg(test)]
+
fn roundtrip<T>(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<u8, 16>, vec);
+
    prop_roundtrip!(PublicKey);
+
    prop_roundtrip!(filter::Filter, filter);
+
    prop_roundtrip!(RepoId);
+
    prop_roundtrip!(Refs);
+
    prop_roundtrip!((String, String), tuple);
+
    prop_roundtrip!(SignedRefs<Unverified>, 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);

-
        qcheck::TestResult::passed()
-
    }
-

-
    #[quickcheck]
-
    fn prop_vec(input: BoundedVec<String, 16>) {
-
        assert_eq!(
-
            BoundedVec::<String, 16>::decode_exact(&input.encode_to_vec()).unwrap(),
-
            input
-
        );
-
    }
+
        roundtrip(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
-
        );
+
        qcheck::TestResult::passed()
    }

    #[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<Unverified>) {
-
        assert_eq!(
-
            SignedRefs::<Unverified>::decode_exact(&input.encode_to_vec()).unwrap(),
-
            input
-
        );
+
        roundtrip(git::Oid::try_from(input.as_slice()).unwrap());
    }

    #[test]
modified 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<Message>) {
@@ -546,28 +543,14 @@ mod tests {
            .quickcheck(property as fn(items: Vec<Message>));
    }

-
    #[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()
    }
}
modified 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();
    }