342 lines
9.4 KiB
Rust
342 lines
9.4 KiB
Rust
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
|
use std::hash::Hash;
|
|
use std::ops::RangeBounds;
|
|
use std::str::FromStr;
|
|
use std::{iter, net};
|
|
|
|
use crypto::test::signer::MockSigner;
|
|
use crypto::{PublicKey, Unverified};
|
|
use cyphernet::addr::tor::OnionAddrV3;
|
|
use cyphernet::EcPk;
|
|
use qcheck::Arbitrary;
|
|
|
|
use crate::collections::RandomMap;
|
|
use crate::identity::doc::Visibility;
|
|
use crate::identity::project::ProjectName;
|
|
use crate::identity::{
|
|
doc::{Doc, DocAt, RawDoc, RepoId},
|
|
project::Project,
|
|
Did,
|
|
};
|
|
use crate::node::address::{AddressType, Source};
|
|
use crate::node::{Address, Alias, KnownAddress, Timestamp, UserAgent};
|
|
use crate::storage;
|
|
use crate::storage::refs::{Refs, RefsAt, SignedRefs};
|
|
use crate::test::storage::{MockRepository, MockStorage};
|
|
use crate::{cob, git};
|
|
|
|
pub fn oid() -> storage::Oid {
|
|
let oid_bytes: [u8; 20] = gen(1);
|
|
storage::Oid::from_sha1(oid_bytes)
|
|
}
|
|
|
|
pub fn entry_id() -> cob::EntryId {
|
|
self::oid()
|
|
}
|
|
|
|
pub fn refstring(len: usize) -> git::fmt::RefString {
|
|
let mut buf = Vec::<u8>::new();
|
|
for _ in 0..len {
|
|
buf.push(fastrand::u8(0x61..0x7a));
|
|
}
|
|
std::str::from_utf8(&buf)
|
|
.unwrap()
|
|
.to_string()
|
|
.try_into()
|
|
.unwrap()
|
|
}
|
|
|
|
pub fn set<T: Eq + Hash + Arbitrary>(range: impl RangeBounds<usize>) -> HashSet<T> {
|
|
let size = fastrand::usize(range);
|
|
let mut set = HashSet::with_capacity(size);
|
|
let mut g = qcheck::Gen::new(size);
|
|
|
|
while set.len() < size {
|
|
set.insert(T::arbitrary(&mut g));
|
|
}
|
|
set
|
|
}
|
|
|
|
pub fn vec<T: Eq + Arbitrary>(size: usize) -> Vec<T> {
|
|
let mut vec = Vec::with_capacity(size);
|
|
let mut g = qcheck::Gen::new(size);
|
|
|
|
for _ in 0..vec.capacity() {
|
|
vec.push(T::arbitrary(&mut g));
|
|
}
|
|
vec
|
|
}
|
|
|
|
pub fn nonempty_storage(size: usize) -> MockStorage {
|
|
let mut storage = gen::<MockStorage>(size);
|
|
for _ in 0..size {
|
|
let doc = gen::<DocAt>(1);
|
|
let id = RepoId::from(doc.blob);
|
|
storage.repos.insert(
|
|
id,
|
|
MockRepository {
|
|
id,
|
|
doc,
|
|
remotes: HashMap::new(),
|
|
},
|
|
);
|
|
}
|
|
storage
|
|
}
|
|
|
|
/// Generate a `String` of length `size`, only containing alphanumeric
|
|
/// characters, i.e. [A-Za-z0-9]
|
|
pub fn alphanumeric(size: usize) -> String {
|
|
let mut s = String::with_capacity(size);
|
|
for _ in 0..size {
|
|
let choice = gen::<u8>(size).clamp(0, 3);
|
|
let c = match choice {
|
|
// Generate A-Z
|
|
0 => gen::<u8>(size).clamp(0x41, 0x5A),
|
|
// Generate a-z
|
|
1 => gen::<u8>(size).clamp(0x61, 0x7A),
|
|
// Generate 0-9
|
|
_ => gen::<u8>(size).clamp(0x30, 0x39),
|
|
};
|
|
s.push(char::from(c));
|
|
}
|
|
s
|
|
}
|
|
|
|
pub fn gen<T: Arbitrary>(size: usize) -> T {
|
|
let mut gen = qcheck::Gen::new(size);
|
|
|
|
T::arbitrary(&mut gen)
|
|
}
|
|
|
|
impl Arbitrary for storage::Remotes<crypto::Unverified> {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let remotes: RandomMap<storage::RemoteId, storage::Remote<crypto::Unverified>> =
|
|
Arbitrary::arbitrary(g);
|
|
|
|
storage::Remotes::new(remotes)
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for Did {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
Self::from(PublicKey::arbitrary(g))
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for Project {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let mut rng = fastrand::Rng::with_seed(u64::arbitrary(g));
|
|
let length = rng.usize(1..16);
|
|
let name: String = iter::repeat_with(|| rng.alphanumeric())
|
|
.take(length)
|
|
.collect();
|
|
let name = ProjectName::from_str(&name).unwrap();
|
|
let description = iter::repeat_with(|| rng.alphanumeric())
|
|
.take(length * 2)
|
|
.collect();
|
|
let default_branch: git::fmt::RefString = iter::repeat_with(|| rng.alphanumeric())
|
|
.take(length)
|
|
.collect::<String>()
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
Project::new(name, description, default_branch).unwrap()
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for Visibility {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
if bool::arbitrary(g) {
|
|
Visibility::Public
|
|
} else {
|
|
Visibility::Private {
|
|
allow: BTreeSet::arbitrary(g),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for RawDoc {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let proj = Project::arbitrary(g);
|
|
let delegate = Did::arbitrary(g);
|
|
let visibility = Visibility::arbitrary(g);
|
|
|
|
Self::new(proj, vec![delegate], 1, visibility)
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for Doc {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let mut rng = fastrand::Rng::with_seed(u64::arbitrary(g));
|
|
let project = Project::arbitrary(g);
|
|
let delegates = iter::repeat_with(|| Did::arbitrary(g))
|
|
.take(rng.usize(1..6))
|
|
.collect::<Vec<_>>();
|
|
let threshold = delegates.len() / 2 + 1;
|
|
let visibility = Visibility::arbitrary(g);
|
|
let doc = RawDoc::new(project, delegates, threshold, visibility);
|
|
|
|
doc.verified().unwrap()
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for DocAt {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let doc = Doc::arbitrary(g);
|
|
|
|
DocAt {
|
|
commit: self::oid(),
|
|
blob: self::oid(),
|
|
doc,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for SignedRefs<Unverified> {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let bytes: [u8; 64] = Arbitrary::arbitrary(g);
|
|
let signature = crypto::Signature::from(bytes);
|
|
let author = PublicKey::arbitrary(g);
|
|
let refs = Refs::arbitrary(g);
|
|
|
|
Self::new(refs, author, signature)
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for Refs {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let mut refs: BTreeMap<git::fmt::RefString, storage::Oid> = BTreeMap::new();
|
|
let mut bytes: [u8; 20] = [0; 20];
|
|
let names = &[
|
|
"heads/master",
|
|
"heads/feature/1",
|
|
"heads/feature/2",
|
|
"heads/feature/3",
|
|
"rad/id",
|
|
"tags/v1.0",
|
|
"tags/v2.0",
|
|
"notes/1",
|
|
];
|
|
|
|
for _ in 0..g.size().min(names.len()) {
|
|
if let Some(name) = g.choose(names) {
|
|
for byte in &mut bytes {
|
|
*byte = u8::arbitrary(g);
|
|
}
|
|
let oid = storage::Oid::from_sha1(bytes);
|
|
let name = git::fmt::RefString::try_from(*name).unwrap();
|
|
|
|
refs.insert(name, oid);
|
|
}
|
|
}
|
|
Self::from(refs)
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for RefsAt {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
Self {
|
|
remote: PublicKey::arbitrary(g),
|
|
at: oid(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for MockStorage {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let inventory = Arbitrary::arbitrary(g);
|
|
MockStorage::new(inventory)
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for MockRepository {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let rid = RepoId::arbitrary(g);
|
|
let doc = Doc::arbitrary(g);
|
|
|
|
Self::new(rid, doc)
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for storage::Remote<crypto::Unverified> {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let refs = Refs::arbitrary(g);
|
|
let signer = MockSigner::arbitrary(g);
|
|
let signed = refs.signed(&signer.into()).unwrap();
|
|
|
|
storage::Remote::<crypto::Unverified>::new(signed)
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for AddressType {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let t = *g.choose(&[1, 2, 3, 4]).unwrap() as u8;
|
|
|
|
AddressType::try_from(t).unwrap()
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for Address {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let host = match AddressType::arbitrary(g) {
|
|
AddressType::Ipv4 => cyphernet::addr::HostName::Ip(net::IpAddr::V4(
|
|
net::Ipv4Addr::from(u32::arbitrary(g)),
|
|
)),
|
|
AddressType::Ipv6 => {
|
|
let octets: [u8; 16] = Arbitrary::arbitrary(g);
|
|
cyphernet::addr::HostName::Ip(net::IpAddr::V6(net::Ipv6Addr::from(octets)))
|
|
}
|
|
AddressType::Dns => cyphernet::addr::HostName::Dns(
|
|
g.choose(&["iris.radicle.example.com", "rosa.radicle.example.com"])
|
|
.unwrap()
|
|
.to_string(),
|
|
),
|
|
AddressType::Onion => {
|
|
let pk = PublicKey::arbitrary(g);
|
|
let addr = OnionAddrV3::from(
|
|
cyphernet::ed25519::PublicKey::from_pk_compressed(**pk).unwrap(),
|
|
);
|
|
cyphernet::addr::HostName::Tor(addr)
|
|
}
|
|
};
|
|
|
|
Address::from(cyphernet::addr::NetAddr {
|
|
host,
|
|
port: u16::arbitrary(g),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for KnownAddress {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
KnownAddress::new(Address::arbitrary(g), Source::Peer)
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for Alias {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
let s = g
|
|
.choose(&["cloudhead", "alice", "bob", "john-lu", "f0_"])
|
|
.unwrap();
|
|
|
|
Alias::from_str(s).unwrap()
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for Timestamp {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
Self::try_from(u64::arbitrary(g).min(*Self::MAX)).unwrap()
|
|
}
|
|
}
|
|
|
|
impl Arbitrary for UserAgent {
|
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
UserAgent::from_str(
|
|
format!("/radicle:1.{}.{}/", u8::arbitrary(g), u8::arbitrary(g)).as_str(),
|
|
)
|
|
.unwrap()
|
|
}
|
|
}
|