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:
parent
a985e067a5
commit
c620f873e5
|
|
@ -1596,6 +1596,7 @@ name = "radicle-cob"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ed25519-compact",
|
"ed25519-compact",
|
||||||
|
"fastrand",
|
||||||
"git-commit",
|
"git-commit",
|
||||||
"git-ref-format",
|
"git-ref-format",
|
||||||
"git-trailers",
|
"git-trailers",
|
||||||
|
|
@ -1603,6 +1604,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"quickcheck",
|
"quickcheck",
|
||||||
|
"quickcheck_macros",
|
||||||
"radicle-crypto",
|
"radicle-crypto",
|
||||||
"radicle-git-ext",
|
"radicle-git-ext",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,11 @@ features = ["derive"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ed25519-compact = { version = "1.0.12", features = ["pem"] }
|
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" }
|
tempfile = { version = "3" }
|
||||||
quickcheck = { version = "1", default-features = false }
|
quickcheck = { version = "1", default-features = false }
|
||||||
|
quickcheck_macros = { version = "1", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies.radicle-crypto]
|
[dev-dependencies.radicle-crypto]
|
||||||
path = "../radicle-crypto"
|
path = "../radicle-crypto"
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,12 @@
|
||||||
//! [automerge]: https://automerge.org
|
//! [automerge]: https://automerge.org
|
||||||
//! [RFC-0662]: https://github.com/radicle-dev/radicle-link/blob/master/docs/rfc/0662-collaborative-objects.adoc
|
//! [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_crypto as crypto;
|
||||||
extern crate radicle_git_ext as git_ext;
|
extern crate radicle_git_ext as git_ext;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,5 @@ pub use identity::{Name, Person, Project, RemoteProject};
|
||||||
|
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
pub use storage::Storage;
|
pub use storage::Storage;
|
||||||
|
|
||||||
|
pub mod arbitrary;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
use crypto::test::signer::MockSigner;
|
use crypto::test::signer::MockSigner;
|
||||||
|
use git_ref_format::{refname, Component, RefString};
|
||||||
use quickcheck::Arbitrary;
|
use quickcheck::Arbitrary;
|
||||||
use radicle_crypto::Signer;
|
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;
|
use super::test;
|
||||||
|
|
||||||
|
|
@ -223,6 +227,69 @@ fn traverse_cobs() {
|
||||||
assert_eq!(contents, vec![b"issue 1".to_vec(), b"issue 2".to_vec()]);
|
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 {
|
fn gen<T: Arbitrary>(size: usize) -> T {
|
||||||
let mut gen = quickcheck::Gen::new(size);
|
let mut gen = quickcheck::Gen::new(size);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue