From 46501507beb069b053b4ff871a6d934da529c76a Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Wed, 21 Sep 2022 15:11:17 +0200 Subject: [PATCH] Split repository into two creates * `radicle`: core library * `radicle-node`: node Signed-off-by: Alexis Sellier --- Cargo.lock | 89 ++++--- Cargo.toml | 2 +- radicle-node/Cargo.toml | 16 +- radicle-node/src/lib.rs | 10 +- radicle-node/src/service.rs | 7 +- radicle-node/src/service/config.rs | 3 +- radicle-node/src/service/message.rs | 2 +- radicle-node/src/test.rs | 9 +- radicle-node/src/test/arbitrary.rs | 233 +---------------- radicle-node/src/test/fixtures.rs | 116 --------- radicle-node/src/test/peer.rs | 4 +- radicle-node/src/test/tests.rs | 7 +- radicle-node/src/wire.rs | 27 +- radicle/Cargo.toml | 37 +++ {radicle-node => radicle}/src/collections.rs | 0 {radicle-node => radicle}/src/crypto.rs | 0 {radicle-node => radicle}/src/git.rs | 1 + {radicle-node => radicle}/src/hash.rs | 0 {radicle-node => radicle}/src/identity.rs | 0 {radicle-node => radicle}/src/identity/doc.rs | 11 +- radicle/src/lib.rs | 10 + {radicle-node => radicle}/src/rad.rs | 10 +- {radicle-node => radicle}/src/serde_ext.rs | 0 {radicle-node => radicle}/src/storage.rs | 0 {radicle-node => radicle}/src/storage/git.rs | 36 +-- {radicle-node => radicle}/src/storage/refs.rs | 30 +-- radicle/src/test.rs | 5 + radicle/src/test/arbitrary.rs | 234 ++++++++++++++++++ {radicle-node => radicle}/src/test/assert.rs | 0 radicle/src/test/fixtures.rs | 73 ++++++ .../crypto.rs => radicle/src/test/signer.rs | 0 {radicle-node => radicle}/src/test/storage.rs | 12 +- 32 files changed, 499 insertions(+), 485 deletions(-) delete mode 100644 radicle-node/src/test/fixtures.rs create mode 100644 radicle/Cargo.toml rename {radicle-node => radicle}/src/collections.rs (100%) rename {radicle-node => radicle}/src/crypto.rs (100%) rename {radicle-node => radicle}/src/git.rs (99%) rename {radicle-node => radicle}/src/hash.rs (100%) rename {radicle-node => radicle}/src/identity.rs (100%) rename {radicle-node => radicle}/src/identity/doc.rs (98%) create mode 100644 radicle/src/lib.rs rename {radicle-node => radicle}/src/rad.rs (97%) rename {radicle-node => radicle}/src/serde_ext.rs (100%) rename {radicle-node => radicle}/src/storage.rs (100%) rename {radicle-node => radicle}/src/storage/git.rs (96%) rename {radicle-node => radicle}/src/storage/refs.rs (93%) create mode 100644 radicle/src/test.rs create mode 100644 radicle/src/test/arbitrary.rs rename {radicle-node => radicle}/src/test/assert.rs (100%) create mode 100644 radicle/src/test/fixtures.rs rename radicle-node/src/test/crypto.rs => radicle/src/test/signer.rs (100%) rename {radicle-node => radicle}/src/test/storage.rs (89%) diff --git a/Cargo.lock b/Cargo.lock index 154ed008..d9355b97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "arrayref" @@ -114,12 +114,6 @@ dependencies = [ "siphasher", ] -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - [[package]] name = "bstr" version = "0.2.17" @@ -277,9 +271,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -496,9 +490,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" [[package]] name = "libgit2-sys" @@ -646,9 +640,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -764,6 +758,31 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radicle" +version = "0.2.0" +dependencies = [ + "ed25519-compact", + "fastrand", + "git-ref-format", + "git-url", + "git2", + "log", + "multibase", + "nonempty", + "olpc-cjson", + "once_cell", + "quickcheck", + "quickcheck_macros", + "radicle-git-ext", + "serde", + "serde_json", + "sha2 0.10.6", + "siphasher", + "tempfile", + "thiserror", +] + [[package]] name = "radicle-git-ext" version = "0.1.0" @@ -784,30 +803,20 @@ version = "0.2.0" dependencies = [ "anyhow", "bloomy", - "bs58", "byteorder", "chrono", "colored", "crossbeam-channel", - "ed25519-compact", "fastrand", - "git-ref-format", - "git-url", - "git2", "log", - "multibase", "nakamoto-net", "nakamoto-net-poll", "nonempty", - "olpc-cjson", - "once_cell", "quickcheck", "quickcheck_macros", - "radicle-git-ext", + "radicle", "serde", "serde_json", - "sha2 0.10.5", - "siphasher", "tempfile", "thiserror", ] @@ -828,9 +837,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] @@ -919,13 +928,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -958,9 +967,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" dependencies = [ "proc-macro2", "quote", @@ -983,18 +992,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" +checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" +checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" dependencies = [ "proc-macro2", "quote", @@ -1041,15 +1050,15 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] diff --git a/Cargo.toml b/Cargo.toml index 13cead69..139b4f77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["radicle-node"] +members = ["radicle", "radicle-node"] [patch.crates-io.radicle-git-ext] git = "https://github.com/radicle-dev/radicle-link" diff --git a/radicle-node/Cargo.toml b/radicle-node/Cargo.toml index 2ad0890a..6c5f45d7 100644 --- a/radicle-node/Cargo.toml +++ b/radicle-node/Cargo.toml @@ -7,32 +7,26 @@ edition = "2021" [dependencies] anyhow = { version = "1" } -bs58 = { version = "0.4.0" } -ed25519-compact = { version = "1.0.12", features = ["pem"] } byteorder = { version = "1" } bloomy = { version = "1.2" } chrono = { version = "0.4.0" } colored = { version = "1.9.0" } crossbeam-channel = { version = "0.5.6" } fastrand = { version = "1.8.0" } -git-ref-format = { version = "0", features = ["serde", "macro"] } -git2 = { version = "0.13" } -git-url = { version = "0.3.5", features = ["serde1"] } -multibase = { version = "0.9.1" } log = { version = "0.4.17", features = ["std"] } -once_cell = { version = "1.13" } -olpc-cjson = { version = "0.1.1" } -sha2 = { version = "0.10.2" } serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["preserve_order"] } -siphasher = { version = "0.3.10" } -radicle-git-ext = { version = "0", features = ["serde"] } nonempty = { version = "0.8.0", features = ["serialize"] } nakamoto-net = { version = "0.3.0" } nakamoto-net-poll = { version = "0.3.0" } tempfile = { version = "3.3.0" } thiserror = { version = "1" } +[dependencies.radicle] +path = "../radicle" +version = "0.2.0" + [dev-dependencies] +radicle = { path = "../radicle", version = "*", features = ["test"] } quickcheck = { version = "1", default-features = false } quickcheck_macros = { version = "1", default-features = false } diff --git a/radicle-node/src/lib.rs b/radicle-node/src/lib.rs index a10a0f49..3da1232f 100644 --- a/radicle-node/src/lib.rs +++ b/radicle-node/src/lib.rs @@ -5,23 +5,17 @@ pub mod address_book; pub mod address_manager; pub mod client; pub mod clock; -pub mod collections; pub mod control; -pub mod crypto; pub mod decoder; -pub mod git; -pub mod hash; -pub mod identity; pub mod logger; -pub mod rad; -pub mod serde_ext; pub mod service; -pub mod storage; #[cfg(test)] pub mod test; pub mod transport; pub mod wire; +pub use radicle::{collections, crypto, git, hash, identity, rad, storage}; + pub mod prelude { pub use crate::crypto::{PublicKey, Signature, Signer}; pub use crate::decoder::Decoder; diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index b6557d0d..5254c36c 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -9,7 +9,6 @@ use std::{collections::VecDeque, fmt, net, net::IpAddr}; use crossbeam_channel as chan; use fastrand::Rng; -use git_url::Url; use log::*; use nakamoto::{LocalDuration, LocalTime}; use nakamoto_net as nakamoto; @@ -22,6 +21,8 @@ use crate::address_manager::AddressManager; use crate::clock::RefClock; use crate::collections::{HashMap, HashSet}; use crate::crypto; +use crate::git; +use crate::git::Url; use crate::identity::{Id, Project}; use crate::service::config::ProjectTracking; use crate::service::message::{NodeAnnouncement, RefsAnnouncement}; @@ -81,7 +82,7 @@ pub enum Event { #[derive(thiserror::Error, Debug)] pub enum FetchError { #[error(transparent)] - Git(#[from] git2::Error), + Git(#[from] git::raw::Error), #[error(transparent)] Storage(#[from] storage::Error), #[error(transparent)] @@ -389,7 +390,7 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service // TODO: Limit the number of seeds we fetch from? Randomize? for (_, peer) in seeds { match repo.fetch(&Url { - scheme: git_url::Scheme::Git, + scheme: git::url::Scheme::Git, host: Some(peer.addr.ip().to_string()), port: Some(peer.addr.port()), // TODO: Fix upstream crate so that it adds a `/` when needed. diff --git a/radicle-node/src/service/config.rs b/radicle-node/src/service/config.rs index 49ecaeff..eb4d7e68 100644 --- a/radicle-node/src/service/config.rs +++ b/radicle-node/src/service/config.rs @@ -1,9 +1,8 @@ use std::net; -use git_url::Url; - use crate::collections::HashSet; use crate::git; +use crate::git::Url; use crate::identity::{Id, PublicKey}; use crate::service::message::{Address, Envelope, Message}; diff --git a/radicle-node/src/service/message.rs b/radicle-node/src/service/message.rs index 21533381..0d3a07f1 100644 --- a/radicle-node/src/service/message.rs +++ b/radicle-node/src/service/message.rs @@ -288,7 +288,7 @@ mod tests { use quickcheck_macros::quickcheck; use crate::crypto::Signer; - use crate::test::crypto::MockSigner; + use crate::test::signer::MockSigner; #[quickcheck] fn prop_refs_announcement_signing(id: Id, refs: Refs) { diff --git a/radicle-node/src/test.rs b/radicle-node/src/test.rs index 8c7cfa0f..2c1a126c 100644 --- a/radicle-node/src/test.rs +++ b/radicle-node/src/test.rs @@ -1,10 +1,11 @@ pub(crate) mod arbitrary; -pub(crate) mod assert; -pub(crate) mod crypto; -pub(crate) mod fixtures; pub(crate) mod handle; pub(crate) mod logger; pub(crate) mod peer; pub(crate) mod simulator; -pub(crate) mod storage; pub(crate) mod tests; + +#[cfg(test)] +pub use radicle::assert_matches; +#[cfg(test)] +pub use radicle::test::*; diff --git a/radicle-node/src/test/arbitrary.rs b/radicle-node/src/test/arbitrary.rs index 08c14418..316ed107 100644 --- a/radicle-node/src/test/arbitrary.rs +++ b/radicle-node/src/test/arbitrary.rs @@ -1,72 +1,20 @@ -use std::collections::{BTreeMap, HashSet}; -use std::hash::Hash; -use std::iter; use std::net; -use std::ops::RangeBounds; -use std::path::PathBuf; use bloomy::BloomFilter; -use nonempty::NonEmpty; use quickcheck::Arbitrary; -use crate::collections::HashMap; use crate::crypto; -use crate::crypto::{KeyPair, PublicKey, Seed, Signer, Unverified, Verified}; -use crate::git; -use crate::hash; -use crate::identity::{doc::Delegate, doc::Doc, Did, Id, Project}; +use crate::identity::Id; use crate::service::filter::{Filter, FILTER_SIZE}; use crate::service::message::{ Address, Envelope, InventoryAnnouncement, Message, NodeAnnouncement, RefsAnnouncement, Subscribe, }; use crate::service::{NodeId, Timestamp}; -use crate::storage; -use crate::storage::refs::{Refs, SignedRefs}; -use crate::test::storage::MockStorage; +use crate::storage::refs::Refs; use crate::wire::message::MessageType; -use super::crypto::MockSigner; - -pub fn set(range: impl RangeBounds) -> HashSet { - let size = fastrand::usize(range); - let mut set = HashSet::with_capacity(size); - let mut g = quickcheck::Gen::new(size); - - while set.len() < size { - set.insert(T::arbitrary(&mut g)); - } - set -} - -pub fn gen(size: usize) -> T { - let mut gen = quickcheck::Gen::new(size); - - T::arbitrary(&mut gen) -} - -#[derive(Clone, Debug)] -pub struct ByteArray([u8; N]); - -impl ByteArray { - pub fn into_inner(self) -> [u8; N] { - self.0 - } - - pub fn as_slice(&self) -> &[u8] { - self.0.as_slice() - } -} - -impl Arbitrary for ByteArray { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let mut bytes: [u8; N] = [0; N]; - for byte in &mut bytes { - *byte = u8::arbitrary(g); - } - Self(bytes) - } -} +pub use radicle::test::arbitrary::*; impl Arbitrary for Filter { fn arbitrary(g: &mut quickcheck::Gen) -> Self { @@ -159,178 +107,3 @@ impl Arbitrary for Address { } } } - -impl Arbitrary for storage::Remotes { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let remotes: HashMap> = - Arbitrary::arbitrary(g); - - storage::Remotes::new(remotes) - } -} - -impl Arbitrary for MockStorage { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let inventory = Arbitrary::arbitrary(g); - MockStorage::new(inventory) - } -} - -impl Arbitrary for Project { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let doc = Doc::::arbitrary(g); - let (oid, _) = doc.encode().unwrap(); - let id = Id::from(oid); - let remotes = storage::Remotes::arbitrary(g); - let path = PathBuf::arbitrary(g); - - Self { - id, - doc, - remotes, - path, - } - } -} - -impl Arbitrary for Did { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - Self::from(PublicKey::arbitrary(g)) - } -} - -impl Arbitrary for Delegate { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - Self { - name: String::arbitrary(g), - id: Did::arbitrary(g), - } - } -} - -impl Arbitrary for Doc { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let name = String::arbitrary(g); - let description = String::arbitrary(g); - let default_branch = String::arbitrary(g); - let delegate = Delegate::arbitrary(g); - - Self::initial(name, description, default_branch, delegate) - } -} - -impl Arbitrary for Doc { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let rng = fastrand::Rng::with_seed(u64::arbitrary(g)); - let name = iter::repeat_with(|| rng.alphanumeric()) - .take(rng.usize(1..16)) - .collect(); - let description = iter::repeat_with(|| rng.alphanumeric()) - .take(rng.usize(0..32)) - .collect(); - let default_branch = iter::repeat_with(|| rng.alphanumeric()) - .take(rng.usize(1..16)) - .collect(); - let delegates: NonEmpty<_> = iter::repeat_with(|| Delegate { - name: iter::repeat_with(|| rng.alphanumeric()) - .take(rng.usize(1..16)) - .collect(), - id: Did::arbitrary(g), - }) - .take(rng.usize(1..6)) - .collect::>() - .try_into() - .unwrap(); - let threshold = delegates.len() / 2 + 1; - let doc: Doc = - Doc::new(name, description, default_branch, delegates, threshold); - - doc.verified().unwrap() - } -} - -impl Arbitrary for SignedRefs { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let bytes: ByteArray<64> = Arbitrary::arbitrary(g); - let signature = crypto::Signature::from(bytes.into_inner()); - let refs = Refs::arbitrary(g); - - Self::new(refs, signature) - } -} - -impl Arbitrary for Refs { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let mut refs: BTreeMap = BTreeMap::new(); - let mut bytes: [u8; 20] = [0; 20]; - let names = &[ - "heads/master", - "heads/feature/1", - "heads/feature/2", - "heads/feature/3", - "heads/radicle/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::try_from(&bytes[..]).unwrap(); - let name = git::RefString::try_from(*name).unwrap(); - - refs.insert(name, oid); - } - } - Self::from(refs) - } -} - -impl Arbitrary for storage::Remote { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let refs = Refs::arbitrary(g); - let signer = MockSigner::arbitrary(g); - let signed = refs.signed(&signer).unwrap(); - - storage::Remote::new(*signer.public_key(), signed) - } -} - -impl Arbitrary for MockSigner { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let bytes: ByteArray<32> = Arbitrary::arbitrary(g); - let seed = Seed::new(bytes.into_inner()); - let sk = KeyPair::from_seed(seed).sk; - - MockSigner::from(sk) - } -} - -impl Arbitrary for Id { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let bytes = ByteArray::<20>::arbitrary(g); - let oid = git::Oid::try_from(bytes.as_slice()).unwrap(); - - Id::from(oid) - } -} - -impl Arbitrary for hash::Digest { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let bytes: Vec = Arbitrary::arbitrary(g); - hash::Digest::new(&bytes) - } -} - -impl Arbitrary for PublicKey { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let bytes: ByteArray<32> = Arbitrary::arbitrary(g); - let seed = Seed::new(bytes.into_inner()); - let keypair = KeyPair::from_seed(seed); - - PublicKey(keypair.pk) - } -} diff --git a/radicle-node/src/test/fixtures.rs b/radicle-node/src/test/fixtures.rs deleted file mode 100644 index 6dc8c209..00000000 --- a/radicle-node/src/test/fixtures.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::path::Path; - -use crate::crypto::{Signer, Verified}; -use crate::git; -use crate::identity::Id; -use crate::rad; -use crate::storage::git::Storage; -use crate::storage::refs::SignedRefs; -use crate::storage::{BranchName, WriteStorage}; -use crate::test::arbitrary; -use crate::test::crypto::MockSigner; - -pub fn storage>(path: P) -> Storage { - let path = path.as_ref(); - let proj_ids = arbitrary::set::(3..=3); - let signers = arbitrary::set::(3..=3); - let storage = Storage::open(path).unwrap(); - - crate::test::logger::init(log::Level::Debug); - - for signer in signers { - let remote = signer.public_key(); - - log::debug!("signer {}...", remote); - - for proj in proj_ids.iter() { - let repo = storage.repository(proj).unwrap(); - let raw = &repo.backend; - let sig = git2::Signature::now(&remote.to_string(), "anonymous@radicle.xyz").unwrap(); - let head = git::initial_commit(raw, &sig).unwrap(); - - log::debug!("{}: creating {}...", remote, proj); - - raw.reference( - &format!("refs/remotes/{remote}/heads/radicle/id"), - head.id(), - false, - "test", - ) - .unwrap(); - - let head = git::commit( - raw, - &head, - &git::RefString::try_from(format!("refs/remotes/{remote}/heads/master")).unwrap(), - "Second commit", - &remote.to_string(), - ) - .unwrap(); - - git::commit( - raw, - &head, - &git::RefString::try_from(format!("refs/remotes/{remote}/heads/patch/3")).unwrap(), - "Third commit", - &remote.to_string(), - ) - .unwrap(); - - storage.sign_refs(&repo, &signer).unwrap(); - } - } - storage -} - -/// Create a new repository at the given path, and initialize it into a project. -pub fn project<'r, P: AsRef, S: WriteStorage<'r>, G: Signer>( - path: P, - storage: &'r S, - signer: G, -) -> Result<(Id, SignedRefs, git2::Repository, git2::Oid), rad::InitError> { - let (repo, head) = repository(path); - let (id, refs) = rad::init( - &repo, - "acme", - "Acme's repository", - BranchName::from("master"), - signer, - storage, - )?; - - Ok((id, refs, repo, head)) -} - -/// Creates a regular repository at the given path with a couple of commits. -pub fn repository>(path: P) -> (git2::Repository, git2::Oid) { - let repo = git2::Repository::init(path).unwrap(); - let sig = git2::Signature::now("anonymous", "anonymous@radicle.xyz").unwrap(); - let head = git::initial_commit(&repo, &sig).unwrap(); - let oid = git::commit( - &repo, - &head, - git::refname!("refs/heads/master").as_refstr(), - "Second commit", - "anonymous", - ) - .unwrap() - .id(); - - // Look, I don't really understand why we have to do this, but we do. - drop(head); - - (repo, oid) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn smoke() { - let tmp = tempfile::tempdir().unwrap(); - - storage(&tmp.path()); - } -} diff --git a/radicle-node/src/test/peer.rs b/radicle-node/src/test/peer.rs index e6dd36c9..bc0590a8 100644 --- a/radicle-node/src/test/peer.rs +++ b/radicle-node/src/test/peer.rs @@ -1,18 +1,18 @@ use std::net; use std::ops::{Deref, DerefMut}; -use git_url::Url; use log::*; use crate::address_book::{KnownAddress, Source}; use crate::clock::RefClock; use crate::collections::HashMap; +use crate::git::Url; use crate::service; use crate::service::config::*; use crate::service::message::*; use crate::service::*; use crate::storage::WriteStorage; -use crate::test::crypto::MockSigner; +use crate::test::signer::MockSigner; use crate::test::simulator; use crate::{Link, LocalTime}; diff --git a/radicle-node/src/test/tests.rs b/radicle-node/src/test/tests.rs index 313a5d8f..a8e1e22d 100644 --- a/radicle-node/src/test/tests.rs +++ b/radicle-node/src/test/tests.rs @@ -11,15 +11,17 @@ use crate::service::peer::*; use crate::service::*; use crate::storage::git::Storage; use crate::storage::ReadStorage; +use crate::test::assert_matches; use crate::test::fixtures; #[allow(unused)] use crate::test::logger; use crate::test::peer::Peer; +use crate::test::signer::MockSigner; use crate::test::simulator; use crate::test::simulator::{Peer as _, Simulation}; use crate::test::storage::MockStorage; -use crate::{assert_matches, Link, LocalTime}; use crate::{client, identity, rad, service, storage, test}; +use crate::{Link, LocalTime}; // NOTE // @@ -136,7 +138,8 @@ fn test_inventory_sync() { [7, 7, 7, 7], Storage::open(tmp.path().join("alice")).unwrap(), ); - let bob_storage = fixtures::storage(tmp.path().join("bob")); + let bob_signer = MockSigner::default(); + let bob_storage = fixtures::storage(tmp.path().join("bob"), bob_signer).unwrap(); let bob = Peer::new("bob", [8, 8, 8, 8], bob_storage); let now = LocalTime::now().as_secs(); let projs = bob.storage().inventory().unwrap(); diff --git a/radicle-node/src/wire.rs b/radicle-node/src/wire.rs index b95cfad0..313fddfb 100644 --- a/radicle-node/src/wire.rs +++ b/radicle-node/src/wire.rs @@ -12,7 +12,7 @@ use nakamoto_net as nakamoto; use nakamoto_net::Link; use crate::address_book; -use crate::crypto::{PublicKey, Signature, Signer}; +use crate::crypto::{PublicKey, Signature, Signer, Unverified}; use crate::decoder::Decoder; use crate::git; use crate::git::fmt; @@ -21,6 +21,7 @@ use crate::identity::Id; use crate::service; use crate::service::filter; use crate::storage::refs::Refs; +use crate::storage::refs::SignedRefs; use crate::storage::WriteStorage; /// The default type we use to represent sizes. @@ -266,7 +267,7 @@ impl Decode for git::Oid { fn decode(reader: &mut R) -> Result { let len = usize::decode(reader)?; #[allow(non_upper_case_globals)] - const expected: usize = mem::size_of::(); + const expected: usize = mem::size_of::(); if len != expected { return Err(Error::InvalidSize { @@ -276,7 +277,7 @@ impl Decode for git::Oid { } let buf: [u8; expected] = Decode::decode(reader)?; - let oid = git2::Oid::from_bytes(&buf).expect("the buffer is exactly the right size"); + let oid = git::raw::Oid::from_bytes(&buf).expect("the buffer is exactly the right size"); let oid = git::Oid::from(oid); Ok(oid) @@ -414,6 +415,26 @@ impl Decode for filter::Filter { } } +impl Encode for SignedRefs { + fn encode(&self, writer: &mut W) -> Result { + let mut n = 0; + + n += self.refs.encode(writer)?; + n += self.signature.encode(writer)?; + + Ok(n) + } +} + +impl Decode for SignedRefs { + fn decode(reader: &mut R) -> Result { + let refs = Refs::decode(reader)?; + let signature = Signature::decode(reader)?; + + Ok(Self::new(refs, signature)) + } +} + #[derive(Debug)] pub struct Wire { inboxes: HashMap, diff --git a/radicle/Cargo.toml b/radicle/Cargo.toml new file mode 100644 index 00000000..38074bf4 --- /dev/null +++ b/radicle/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "radicle" +license = "MIT OR Apache-2.0" +version = "0.2.0" +authors = ["Alexis Sellier "] +edition = "2021" + +[features] +default = [] +test = ["quickcheck"] + +[dependencies] +ed25519-compact = { version = "1.0.12", features = ["pem"] } +fastrand = { version = "1.8.0" } +git-ref-format = { version = "0", features = ["serde", "macro"] } +git2 = { version = "0.13" } +git-url = { version = "0.3.5", features = ["serde1"] } +multibase = { version = "0.9.1" } +log = { version = "0.4.17", features = ["std"] } +once_cell = { version = "1.13" } +olpc-cjson = { version = "0.1.1" } +sha2 = { version = "0.10.2" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["preserve_order"] } +siphasher = { version = "0.3.10" } +radicle-git-ext = { version = "0", features = ["serde"] } +nonempty = { version = "0.8.0", features = ["serialize"] } +tempfile = { version = "3.3.0" } +thiserror = { version = "1" } + +[dependencies.quickcheck] +version = "1" +default-features = false +optional = true + +[dev-dependencies] +quickcheck_macros = { version = "1", default-features = false } diff --git a/radicle-node/src/collections.rs b/radicle/src/collections.rs similarity index 100% rename from radicle-node/src/collections.rs rename to radicle/src/collections.rs diff --git a/radicle-node/src/crypto.rs b/radicle/src/crypto.rs similarity index 100% rename from radicle-node/src/crypto.rs rename to radicle/src/crypto.rs diff --git a/radicle-node/src/git.rs b/radicle/src/git.rs similarity index 99% rename from radicle-node/src/git.rs rename to radicle/src/git.rs index c8bdd9fd..50961d71 100644 --- a/radicle-node/src/git.rs +++ b/radicle/src/git.rs @@ -11,6 +11,7 @@ use crate::storage::RemoteId; pub use ext::Error; pub use ext::Oid; +pub use git2 as raw; pub use git_ref_format as fmt; pub use git_ref_format::{refname, RefStr, RefString}; pub use git_url as url; diff --git a/radicle-node/src/hash.rs b/radicle/src/hash.rs similarity index 100% rename from radicle-node/src/hash.rs rename to radicle/src/hash.rs diff --git a/radicle-node/src/identity.rs b/radicle/src/identity.rs similarity index 100% rename from radicle-node/src/identity.rs rename to radicle/src/identity.rs diff --git a/radicle-node/src/identity/doc.rs b/radicle/src/identity/doc.rs similarity index 98% rename from radicle-node/src/identity/doc.rs rename to radicle/src/identity/doc.rs index 3c6b0a85..a3991292 100644 --- a/radicle-node/src/identity/doc.rs +++ b/radicle/src/identity/doc.rs @@ -476,11 +476,12 @@ impl Identity { #[cfg(test)] mod test { - use crate::prelude::Signer; + use crate::crypto::Signer; use crate::rad; use crate::storage::git::Storage; use crate::storage::{ReadStorage, WriteStorage}; - use crate::test::{crypto, fixtures}; + use crate::test::fixtures; + use crate::test::signer::MockSigner; use super::*; use quickcheck_macros::quickcheck; @@ -490,9 +491,9 @@ mod test { let tempdir = tempfile::tempdir().unwrap(); let mut rng = fastrand::Rng::new(); - let alice = crypto::MockSigner::new(&mut rng); - let bob = crypto::MockSigner::new(&mut rng); - let eve = crypto::MockSigner::new(&mut rng); + let alice = MockSigner::new(&mut rng); + let bob = MockSigner::new(&mut rng); + let eve = MockSigner::new(&mut rng); let storage = Storage::open(tempdir.path().join("storage")).unwrap(); let (id, _, _, _) = diff --git a/radicle/src/lib.rs b/radicle/src/lib.rs new file mode 100644 index 00000000..7226b395 --- /dev/null +++ b/radicle/src/lib.rs @@ -0,0 +1,10 @@ +pub mod collections; +pub mod crypto; +pub mod git; +pub mod hash; +pub mod identity; +pub mod rad; +pub mod serde_ext; +pub mod storage; +#[cfg(feature = "test")] +pub mod test; diff --git a/radicle-node/src/rad.rs b/radicle/src/rad.rs similarity index 97% rename from radicle-node/src/rad.rs rename to radicle/src/rad.rs index 815f1707..d96fc8f3 100644 --- a/radicle-node/src/rad.rs +++ b/radicle/src/rad.rs @@ -207,12 +207,12 @@ mod tests { use crate::identity::{Delegate, Did}; use crate::storage::git::Storage; use crate::storage::{ReadStorage, WriteStorage}; - use crate::test::{crypto, fixtures}; + use crate::test::{fixtures, signer::MockSigner}; #[test] fn test_init() { let tempdir = tempfile::tempdir().unwrap(); - let signer = crypto::MockSigner::default(); + let signer = MockSigner::default(); let public_key = *signer.public_key(); let storage = Storage::open(tempdir.path().join("storage")).unwrap(); let (repo, _) = fixtures::repository(tempdir.path().join("working")); @@ -247,9 +247,9 @@ mod tests { fn test_fork() { let mut rng = fastrand::Rng::new(); let tempdir = tempfile::tempdir().unwrap(); - let alice = crypto::MockSigner::new(&mut rng); + let alice = MockSigner::new(&mut rng); let alice_id = alice.public_key(); - let bob = crypto::MockSigner::new(&mut rng); + let bob = MockSigner::new(&mut rng); let bob_id = bob.public_key(); let storage = Storage::open(tempdir.path().join("storage")).unwrap(); let (original, _) = fixtures::repository(tempdir.path().join("original")); @@ -280,7 +280,7 @@ mod tests { #[test] fn test_checkout() { let tempdir = tempfile::tempdir().unwrap(); - let signer = crypto::MockSigner::default(); + let signer = MockSigner::default(); let remote_id = signer.public_key(); let storage = Storage::open(tempdir.path().join("storage")).unwrap(); let (original, _) = fixtures::repository(tempdir.path().join("original")); diff --git a/radicle-node/src/serde_ext.rs b/radicle/src/serde_ext.rs similarity index 100% rename from radicle-node/src/serde_ext.rs rename to radicle/src/serde_ext.rs diff --git a/radicle-node/src/storage.rs b/radicle/src/storage.rs similarity index 100% rename from radicle-node/src/storage.rs rename to radicle/src/storage.rs diff --git a/radicle-node/src/storage/git.rs b/radicle/src/storage/git.rs similarity index 96% rename from radicle-node/src/storage/git.rs rename to radicle/src/storage/git.rs index 4c2be51d..e51f6c66 100644 --- a/radicle-node/src/storage/git.rs +++ b/radicle/src/storage/git.rs @@ -5,12 +5,10 @@ use std::{fmt, fs, io}; use git_ref_format::refspec; use once_cell::sync::Lazy; -pub use radicle_git_ext::Oid; - use crate::crypto::{Signer, Unverified, Verified}; use crate::git; -use crate::identity::{self, Doc}; -use crate::identity::{Id, Project}; +use crate::identity; +use crate::identity::{Doc, Id, Project}; use crate::storage::refs; use crate::storage::refs::{Refs, SignedRefs}; use crate::storage::{ @@ -18,6 +16,8 @@ use crate::storage::{ WriteStorage, }; +pub use crate::git::*; + use super::{RefUpdate, RemoteId}; pub static REMOTES_GLOB: Lazy = @@ -50,12 +50,12 @@ impl fmt::Debug for Storage { } impl ReadStorage for Storage { - fn url(&self) -> git::Url { - git::Url { + fn url(&self) -> Url { + Url { scheme: git_url::Scheme::File, host: None, path: self.path.to_string_lossy().to_string().into(), - ..git::Url::default() + ..Url::default() } } @@ -167,7 +167,7 @@ pub enum VerifyError { #[error("invalid remote `{0}`")] InvalidRemote(RemoteId), #[error("invalid target `{2}` for reference `{1}` of remote `{0}`")] - InvalidRefTarget(RemoteId, git::RefString, git2::Oid), + InvalidRefTarget(RemoteId, RefString, git2::Oid), #[error("invalid reference")] InvalidRef, #[error("ref error: {0}")] @@ -185,7 +185,7 @@ pub enum VerifyError { impl Repository { pub fn open>(path: P) -> Result { let backend = match git2::Repository::open_bare(path.as_ref()) { - Err(e) if git::ext::is_not_found_err(&e) => { + Err(e) if ext::is_not_found_err(&e) => { let backend = git2::Repository::init_opts( path, git2::RepositoryInitOptions::new() @@ -239,7 +239,7 @@ impl Repository { .remove(&refname) .ok_or_else(|| VerifyError::UnknownRef(remote_id, refname.clone()))?; - if git::Oid::from(oid) != signed_oid { + if Oid::from(oid) != signed_oid { return Err(VerifyError::InvalidRefTarget(remote_id, refname, oid)); } } @@ -278,7 +278,7 @@ impl Repository { } /// Return the canonical identity [`git::Oid`] and document. - pub fn identity(&self) -> Result<(git::Oid, identity::Doc), IdentityError> { + pub fn identity(&self) -> Result<(Oid, identity::Doc), IdentityError> { let mut heads = Vec::new(); for remote in self.remote_ids()? { let remote = remote?; @@ -589,19 +589,20 @@ mod tests { use crate::assert_matches; use crate::git; use crate::storage::refs::SIGNATURE_REF; - use crate::storage::{ReadStorage, RefUpdate, WriteRepository}; + use crate::storage::{ReadRepository, ReadStorage, RefUpdate, WriteRepository}; use crate::test::arbitrary; - use crate::test::crypto::MockSigner; use crate::test::fixtures; + use crate::test::signer::MockSigner; #[test] fn test_remote_refs() { let dir = tempfile::tempdir().unwrap(); - let storage = fixtures::storage(dir.path()); + let signer = MockSigner::default(); + let storage = fixtures::storage(dir.path(), &signer).unwrap(); let inv = storage.inventory().unwrap(); let proj = inv.first().unwrap(); let mut refs = git::remote_refs(&git::Url { - host: Some(dir.path().to_string_lossy().to_string()), + host: Some(storage.path().to_string_lossy().to_string()), scheme: git_url::Scheme::File, path: format!("/{}", proj).into(), ..git::Url::default() @@ -627,7 +628,8 @@ mod tests { #[test] fn test_fetch() { let tmp = tempfile::tempdir().unwrap(); - let alice = fixtures::storage(tmp.path().join("alice")); + let alice_signer = MockSigner::default(); + let alice = fixtures::storage(tmp.path().join("alice"), alice_signer).unwrap(); let bob = Storage::open(tmp.path().join("bob")).unwrap(); let inventory = alice.inventory().unwrap(); let proj = inventory.first().unwrap(); @@ -652,7 +654,7 @@ mod tests { .unwrap(); // Four refs are created for each remote. - assert_eq!(updates.len(), remotes.len() * 4); + assert_eq!(updates.len(), remotes.len() * 3); for update in updates { assert_matches!( diff --git a/radicle-node/src/storage/refs.rs b/radicle/src/storage/refs.rs similarity index 93% rename from radicle-node/src/storage/refs.rs rename to radicle/src/storage/refs.rs index 52aaedd5..515f1842 100644 --- a/radicle-node/src/storage/refs.rs +++ b/radicle/src/storage/refs.rs @@ -17,7 +17,6 @@ use crate::git; use crate::git::Oid; use crate::storage; use crate::storage::{ReadRepository, RemoteId, WriteRepository}; -use crate::wire; pub static SIGNATURE_REF: Lazy = Lazy::new(|| git::refname!("radicle/signature")); pub const REFS_BLOB_PATH: &str = "refs"; @@ -177,8 +176,9 @@ impl DerefMut for Refs { /// [`Unverified`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SignedRefs { - refs: Refs, - signature: Signature, + pub refs: Refs, + pub signature: Signature, + _verified: PhantomData, } @@ -315,30 +315,6 @@ impl SignedRefs { } } -impl wire::Encode for SignedRefs { - fn encode(&self, writer: &mut W) -> Result { - let mut n = 0; - - n += self.refs.encode(writer)?; - n += self.signature.encode(writer)?; - - Ok(n) - } -} - -impl wire::Decode for SignedRefs { - fn decode(reader: &mut R) -> Result { - let refs = Refs::decode(reader)?; - let signature = Signature::decode(reader)?; - - Ok(Self { - refs, - signature, - _verified: PhantomData, - }) - } -} - impl Deref for SignedRefs { type Target = Refs; diff --git a/radicle/src/test.rs b/radicle/src/test.rs new file mode 100644 index 00000000..2fd776b2 --- /dev/null +++ b/radicle/src/test.rs @@ -0,0 +1,5 @@ +pub mod arbitrary; +pub mod assert; +pub mod fixtures; +pub mod signer; +pub mod storage; diff --git a/radicle/src/test/arbitrary.rs b/radicle/src/test/arbitrary.rs new file mode 100644 index 00000000..2e24d9d6 --- /dev/null +++ b/radicle/src/test/arbitrary.rs @@ -0,0 +1,234 @@ +use std::collections::{BTreeMap, HashSet}; +use std::hash::Hash; +use std::iter; +use std::ops::RangeBounds; +use std::path::PathBuf; + +use nonempty::NonEmpty; +use quickcheck::Arbitrary; + +use crate::collections::HashMap; +use crate::crypto; +use crate::crypto::{KeyPair, PublicKey, Seed, Signer, Unverified, Verified}; +use crate::git; +use crate::hash; +use crate::identity::{doc::Delegate, doc::Doc, Did, Id, Project}; +use crate::storage; +use crate::storage::refs::{Refs, SignedRefs}; +use crate::test::signer::MockSigner; +use crate::test::storage::MockStorage; + +pub fn set(range: impl RangeBounds) -> HashSet { + let size = fastrand::usize(range); + let mut set = HashSet::with_capacity(size); + let mut g = quickcheck::Gen::new(size); + + while set.len() < size { + set.insert(T::arbitrary(&mut g)); + } + set +} + +pub fn gen(size: usize) -> T { + let mut gen = quickcheck::Gen::new(size); + + T::arbitrary(&mut gen) +} + +#[derive(Clone, Debug)] +pub struct ByteArray([u8; N]); + +impl ByteArray { + pub fn into_inner(self) -> [u8; N] { + self.0 + } + + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl Arbitrary for ByteArray { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let mut bytes: [u8; N] = [0; N]; + for byte in &mut bytes { + *byte = u8::arbitrary(g); + } + Self(bytes) + } +} + +impl Arbitrary for storage::Remotes { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let remotes: HashMap> = + Arbitrary::arbitrary(g); + + storage::Remotes::new(remotes) + } +} + +impl Arbitrary for Project { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let doc = Doc::::arbitrary(g); + let (oid, _) = doc.encode().unwrap(); + let id = Id::from(oid); + let remotes = storage::Remotes::arbitrary(g); + let path = PathBuf::arbitrary(g); + + Self { + id, + doc, + remotes, + path, + } + } +} + +impl Arbitrary for Did { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + Self::from(PublicKey::arbitrary(g)) + } +} + +impl Arbitrary for Delegate { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + Self { + name: String::arbitrary(g), + id: Did::arbitrary(g), + } + } +} + +impl Arbitrary for Doc { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let name = String::arbitrary(g); + let description = String::arbitrary(g); + let default_branch = String::arbitrary(g); + let delegate = Delegate::arbitrary(g); + + Self::initial(name, description, default_branch, delegate) + } +} + +impl Arbitrary for Doc { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let rng = fastrand::Rng::with_seed(u64::arbitrary(g)); + let name = iter::repeat_with(|| rng.alphanumeric()) + .take(rng.usize(1..16)) + .collect(); + let description = iter::repeat_with(|| rng.alphanumeric()) + .take(rng.usize(0..32)) + .collect(); + let default_branch = iter::repeat_with(|| rng.alphanumeric()) + .take(rng.usize(1..16)) + .collect(); + let delegates: NonEmpty<_> = iter::repeat_with(|| Delegate { + name: iter::repeat_with(|| rng.alphanumeric()) + .take(rng.usize(1..16)) + .collect(), + id: Did::arbitrary(g), + }) + .take(rng.usize(1..6)) + .collect::>() + .try_into() + .unwrap(); + let threshold = delegates.len() / 2 + 1; + let doc: Doc = + Doc::new(name, description, default_branch, delegates, threshold); + + doc.verified().unwrap() + } +} + +impl Arbitrary for SignedRefs { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let bytes: ByteArray<64> = Arbitrary::arbitrary(g); + let signature = crypto::Signature::from(bytes.into_inner()); + let refs = Refs::arbitrary(g); + + Self::new(refs, signature) + } +} + +impl Arbitrary for Refs { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let mut refs: BTreeMap = BTreeMap::new(); + let mut bytes: [u8; 20] = [0; 20]; + let names = &[ + "heads/master", + "heads/feature/1", + "heads/feature/2", + "heads/feature/3", + "heads/radicle/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::try_from(&bytes[..]).unwrap(); + let name = git::RefString::try_from(*name).unwrap(); + + refs.insert(name, oid); + } + } + Self::from(refs) + } +} + +impl Arbitrary for MockSigner { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let bytes: ByteArray<32> = Arbitrary::arbitrary(g); + let seed = Seed::new(bytes.into_inner()); + let sk = KeyPair::from_seed(seed).sk; + + MockSigner::from(sk) + } +} + +impl Arbitrary for MockStorage { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let inventory = Arbitrary::arbitrary(g); + MockStorage::new(inventory) + } +} + +impl Arbitrary for storage::Remote { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let refs = Refs::arbitrary(g); + let signer = MockSigner::arbitrary(g); + let signed = refs.signed(&signer).unwrap(); + + storage::Remote::new(*signer.public_key(), signed) + } +} + +impl Arbitrary for Id { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let bytes = ByteArray::<20>::arbitrary(g); + let oid = git::Oid::try_from(bytes.as_slice()).unwrap(); + + Id::from(oid) + } +} + +impl Arbitrary for hash::Digest { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let bytes: Vec = Arbitrary::arbitrary(g); + hash::Digest::new(&bytes) + } +} + +impl Arbitrary for PublicKey { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let bytes: ByteArray<32> = Arbitrary::arbitrary(g); + let seed = Seed::new(bytes.into_inner()); + let keypair = KeyPair::from_seed(seed); + + PublicKey(keypair.pk) + } +} diff --git a/radicle-node/src/test/assert.rs b/radicle/src/test/assert.rs similarity index 100% rename from radicle-node/src/test/assert.rs rename to radicle/src/test/assert.rs diff --git a/radicle/src/test/fixtures.rs b/radicle/src/test/fixtures.rs new file mode 100644 index 00000000..1e9ff0e0 --- /dev/null +++ b/radicle/src/test/fixtures.rs @@ -0,0 +1,73 @@ +use std::path::Path; + +use crate::crypto::{Signer, Verified}; +use crate::git; +use crate::identity::Id; +use crate::rad; +use crate::storage::git::Storage; +use crate::storage::refs::SignedRefs; +use crate::storage::{BranchName, WriteStorage}; + +/// Create a new storage with a project. +pub fn storage, G: Signer>(path: P, signer: G) -> Result { + let path = path.as_ref(); + let storage = Storage::open(path.join("storage"))?; + + for (name, desc) in [ + ("acme", "Acme's repository"), + ("vim", "A text editor"), + ("rx", "A pixel editor"), + ] { + let (repo, _) = repository(path.join("workdir").join(name)); + rad::init( + &repo, + name, + desc, + BranchName::from("master"), + &signer, + &storage, + )?; + } + + Ok(storage) +} + +/// Create a new repository at the given path, and initialize it into a project. +pub fn project<'r, P: AsRef, S: WriteStorage<'r>, G: Signer>( + path: P, + storage: &'r S, + signer: G, +) -> Result<(Id, SignedRefs, git2::Repository, git2::Oid), rad::InitError> { + let (repo, head) = repository(path); + let (id, refs) = rad::init( + &repo, + "acme", + "Acme's repository", + BranchName::from("master"), + signer, + storage, + )?; + + Ok((id, refs, repo, head)) +} + +/// Creates a regular repository at the given path with a couple of commits. +pub fn repository>(path: P) -> (git2::Repository, git2::Oid) { + let repo = git2::Repository::init(path).unwrap(); + let sig = git2::Signature::now("anonymous", "anonymous@radicle.xyz").unwrap(); + let head = git::initial_commit(&repo, &sig).unwrap(); + let oid = git::commit( + &repo, + &head, + git::refname!("refs/heads/master").as_refstr(), + "Second commit", + "anonymous", + ) + .unwrap() + .id(); + + // Look, I don't really understand why we have to do this, but we do. + drop(head); + + (repo, oid) +} diff --git a/radicle-node/src/test/crypto.rs b/radicle/src/test/signer.rs similarity index 100% rename from radicle-node/src/test/crypto.rs rename to radicle/src/test/signer.rs diff --git a/radicle-node/src/test/storage.rs b/radicle/src/test/storage.rs similarity index 89% rename from radicle-node/src/test/storage.rs rename to radicle/src/test/storage.rs index fe0a996a..21eb3224 100644 --- a/radicle-node/src/test/storage.rs +++ b/radicle/src/test/storage.rs @@ -1,13 +1,9 @@ use git_url::Url; use crate::crypto::{Signer, Verified}; -use crate::git; use crate::identity::{Id, Project}; -use crate::storage::{refs, RefUpdate}; -use crate::storage::{ - Error, FetchError, Inventory, ReadRepository, ReadStorage, Remote, RemoteId, WriteRepository, - WriteStorage, -}; + +pub use crate::storage::*; #[derive(Clone, Debug)] pub struct MockStorage { @@ -90,11 +86,11 @@ impl ReadRepository<'_> for MockRepository { todo!() } - fn commit(&self, _oid: git::Oid) -> Result, git2::Error> { + fn commit(&self, _oid: Oid) -> Result, git2::Error> { todo!() } - fn revwalk(&self, _head: git::Oid) -> Result { + fn revwalk(&self, _head: Oid) -> Result { todo!() }