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 <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2022-11-17 09:36:31 +00:00
parent a985e067a5
commit c620f873e5
No known key found for this signature in database
GPG Key ID: 2552FB6F64066CB7
6 changed files with 132 additions and 1 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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;

View File

@ -3,3 +3,5 @@ pub use identity::{Name, Person, Project, RemoteProject};
pub mod storage;
pub use storage::Storage;
pub mod arbitrary;

View File

@ -0,0 +1,51 @@
use std::{iter, marker::PhantomData};
use quickcheck::Arbitrary;
use crate::{ObjectId, TypeName};
#[derive(Clone, Debug)]
pub struct Invalid<T> {
pub value: String,
_marker: PhantomData<T>,
}
impl Arbitrary for TypeName {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
let mut name: Vec<String> = 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::<TypeName>()
.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::<Vec<_>>();
Self::from(git_ext::Oid::try_from(bytes.as_slice()).unwrap())
}
}
impl Arbitrary for Invalid<ObjectId> {
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,
}
}
}

View File

@ -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/<typename>/<object_id> gives back the <typename> and <object_id>
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<ObjectId>, 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<T: Arbitrary>(size: usize) -> T {
let mut gen = quickcheck::Gen::new(size);