From c620f873e5a907b5919c70c5b887a6a3a7085cb6 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Thu, 17 Nov 2022 09:36:31 +0000 Subject: [PATCH] radicle-cob: test parse_refstr Add quickcheck tests for parse_refstr to ensure the correct behaviour. This required adding Arbitrary instances for TypeName and ObjectId, as well as adding a mechanism for generating Invalid ObjectIds. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- Cargo.lock | 2 + radicle-cob/Cargo.toml | 3 ++ radicle-cob/src/lib.rs | 6 +++ radicle-cob/src/test.rs | 2 + radicle-cob/src/test/arbitrary.rs | 51 +++++++++++++++++++++++ radicle-cob/src/tests.rs | 69 ++++++++++++++++++++++++++++++- 6 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 radicle-cob/src/test/arbitrary.rs diff --git a/Cargo.lock b/Cargo.lock index ecabd202..52d01d04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1596,6 +1596,7 @@ name = "radicle-cob" version = "0.1.0" dependencies = [ "ed25519-compact", + "fastrand", "git-commit", "git-ref-format", "git-trailers", @@ -1603,6 +1604,7 @@ dependencies = [ "log", "petgraph", "quickcheck", + "quickcheck_macros", "radicle-crypto", "radicle-git-ext", "serde", diff --git a/radicle-cob/Cargo.toml b/radicle-cob/Cargo.toml index 399cfc96..eb0afc41 100644 --- a/radicle-cob/Cargo.toml +++ b/radicle-cob/Cargo.toml @@ -38,8 +38,11 @@ features = ["derive"] [dev-dependencies] ed25519-compact = { version = "1.0.12", features = ["pem"] } +fastrand = { version = "1.8.0", default-features = false } +git-ref-format = { version = "0.1", features = ["macro"] } tempfile = { version = "3" } quickcheck = { version = "1", default-features = false } +quickcheck_macros = { version = "1", default-features = false } [dev-dependencies.radicle-crypto] path = "../radicle-crypto" diff --git a/radicle-cob/src/lib.rs b/radicle-cob/src/lib.rs index 8483b907..2b5d74e6 100644 --- a/radicle-cob/src/lib.rs +++ b/radicle-cob/src/lib.rs @@ -71,6 +71,12 @@ //! [automerge]: https://automerge.org //! [RFC-0662]: https://github.com/radicle-dev/radicle-link/blob/master/docs/rfc/0662-collaborative-objects.adoc +#[cfg(test)] +extern crate quickcheck; +#[cfg(test)] +#[macro_use(quickcheck)] +extern crate quickcheck_macros; + extern crate radicle_crypto as crypto; extern crate radicle_git_ext as git_ext; diff --git a/radicle-cob/src/test.rs b/radicle-cob/src/test.rs index 6a5e11f8..0e576506 100644 --- a/radicle-cob/src/test.rs +++ b/radicle-cob/src/test.rs @@ -3,3 +3,5 @@ pub use identity::{Name, Person, Project, RemoteProject}; pub mod storage; pub use storage::Storage; + +pub mod arbitrary; diff --git a/radicle-cob/src/test/arbitrary.rs b/radicle-cob/src/test/arbitrary.rs new file mode 100644 index 00000000..3bb96a6d --- /dev/null +++ b/radicle-cob/src/test/arbitrary.rs @@ -0,0 +1,51 @@ +use std::{iter, marker::PhantomData}; + +use quickcheck::Arbitrary; + +use crate::{ObjectId, TypeName}; + +#[derive(Clone, Debug)] +pub struct Invalid { + pub value: String, + _marker: PhantomData, +} + +impl Arbitrary for TypeName { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let rng = fastrand::Rng::with_seed(u64::arbitrary(g)); + let mut name: Vec = Vec::new(); + for _ in 0..rng.usize(1..5) { + name.push( + iter::repeat_with(|| rng.alphanumeric()) + .take(rng.usize(1..16)) + .collect(), + ); + } + name.join(".") + .parse::() + .expect("TypeName is valid") + } +} + +impl Arbitrary for ObjectId { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let rng = fastrand::Rng::with_seed(u64::arbitrary(g)); + let bytes = iter::repeat_with(|| rng.u8(..)) + .take(20) + .collect::>(); + Self::from(git_ext::Oid::try_from(bytes.as_slice()).unwrap()) + } +} + +impl Arbitrary for Invalid { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let rng = fastrand::Rng::with_seed(u64::arbitrary(g)); + let value = iter::repeat_with(|| rng.alphanumeric()) + .take(rng.usize(21..50)) + .collect(); + Invalid { + value, + _marker: PhantomData, + } + } +} diff --git a/radicle-cob/src/tests.rs b/radicle-cob/src/tests.rs index f8dc0262..10299cf4 100644 --- a/radicle-cob/src/tests.rs +++ b/radicle-cob/src/tests.rs @@ -1,10 +1,14 @@ use std::ops::ControlFlow; use crypto::test::signer::MockSigner; +use git_ref_format::{refname, Component, RefString}; use quickcheck::Arbitrary; use radicle_crypto::Signer; -use crate::{create, get, history, list, update, Create, ObjectId, TypeName, Update}; +use crate::{ + create, get, history, list, object, test::arbitrary::Invalid, update, Create, ObjectId, + TypeName, Update, +}; use super::test; @@ -223,6 +227,69 @@ fn traverse_cobs() { assert_eq!(contents, vec![b"issue 1".to_vec(), b"issue 2".to_vec()]); } +#[quickcheck] +fn parse_refstr(oid: ObjectId, typename: TypeName) { + let suffix = refname!("refs/cobs") + .and(Component::from(&typename)) + .and(Component::from(&oid)); + + // refs/cobs// gives back the and + assert_eq!(object::parse_refstr(&suffix), Some((typename.clone(), oid))); + + // strips a single namespace + assert_eq!( + object::parse_refstr(&refname!("refs/namespaces/a").join(&suffix)), + Some((typename.clone(), oid)) + ); + + // strips multiple namespaces + assert_eq!( + object::parse_refstr(&refname!("refs/namespaces/a/refs/namespaces/b").join(&suffix)), + Some((typename.clone(), oid)) + ); + + // ignores the extra path + assert_eq!( + object::parse_refstr( + &refname!("refs/namespaces/a/refs/namespaces/b") + .join(suffix) + .and(refname!("more/paths")) + ), + Some((typename, oid)) + ); +} + +/// Note: an invalid type name is also an invalid reference string, it +/// cannot start or end with a '.', and cannot have '..'. +#[quickcheck] +fn invalid_parse_refstr(oid: Invalid, typename: TypeName) { + let oid = RefString::try_from(oid.value).unwrap(); + let typename = Component::from(&typename); + let suffix = refname!("refs/cobs").and(typename).and(oid); + + // All parsing will fail because `oid` is not a valid ObjectId + assert_eq!(object::parse_refstr(&suffix), None); + + assert_eq!( + object::parse_refstr(&refname!("refs/namespaces/a").join(&suffix)), + None + ); + + assert_eq!( + object::parse_refstr(&refname!("refs/namespaces/a/refs/namespaces/b").join(&suffix)), + None + ); + + assert_eq!( + object::parse_refstr( + &refname!("refs/namespaces/a/refs/namespaces/b") + .join(suffix) + .and(refname!("more/paths")) + ), + None + ); +} + fn gen(size: usize) -> T { let mut gen = quickcheck::Gen::new(size);