Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: More realistic concurrent fetch test
Alexis Sellier committed 3 years ago
commit ea4294c79f64a2a5739b1fca98d98427b91846a3
parent 8205f8ad4e26167a014add81b2cdd323ff32eabe
4 files changed +107 -27
modified radicle-node/src/test/environment.rs
@@ -59,6 +59,16 @@ impl Environment {
        self.tempdir.path().join("misc")
    }

+
    /// Get the scale or "test size". This is used to scale tests with more data. Defaults to `1`.
+
    pub fn scale(&self) -> usize {
+
        env::var("RAD_TEST_SCALE")
+
            .map(|s| {
+
                s.parse()
+
                    .expect("repository: invalid value for `RAD_TEST_SCALE`")
+
            })
+
            .unwrap_or(1)
+
    }
+

    /// Create a new node in this environment. This should be used when a running node
    /// is required. Use [`Environment::profile`] otherwise.
    pub fn node(&mut self, name: &str) -> Node<MemorySigner> {
@@ -318,15 +328,17 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer + Clone> Node<G> {
        }
    }

-
    /// Populate a storage instance with a project.
-
    pub fn project(&mut self, name: &str, description: &str) -> Id {
+
    /// Populate a storage instance with a project from the given repository.
+
    pub fn project_from(
+
        &mut self,
+
        name: &str,
+
        description: &str,
+
        repo: &git::raw::Repository,
+
    ) -> Id {
        transport::local::register(self.storage.clone());

-
        let tmp = tempfile::tempdir().unwrap();
-
        let (repo, _) = fixtures::repository(tmp.path());
-

        let id = rad::init(
-
            &repo,
+
            repo,
            name,
            description,
            refname!("master"),
@@ -341,8 +353,29 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer + Clone> Node<G> {
            "Initialized project {id} for node {}", self.signer.public_key()
        );

+
        // Push local branches to storage.
+
        let mut refs = Vec::<(git::Qualified, git::Qualified)>::new();
+
        for branch in repo.branches(Some(git::raw::BranchType::Local)).unwrap() {
+
            let (branch, _) = branch.unwrap();
+
            let name = git::RefString::try_from(branch.name().unwrap().unwrap()).unwrap();
+

+
            refs.push((
+
                git::lit::refs_heads(&name).into(),
+
                git::lit::refs_heads(&name).into(),
+
            ));
+
        }
+
        git::push(repo, "rad", refs.iter().map(|(a, b)| (a, b))).unwrap();
+

        id
    }
+

+
    /// Populate a storage instance with a project.
+
    pub fn project(&mut self, name: &str, description: &str) -> Id {
+
        let tmp = tempfile::tempdir().unwrap();
+
        let (repo, _) = fixtures::repository(tmp.path());
+

+
        self.project_from(name, description, &repo)
+
    }
}

/// Checks whether the nodes have converged in their routing tables.
modified radicle-node/src/tests/e2e.rs
@@ -2,14 +2,14 @@ use std::{collections::HashSet, thread, time};

use radicle::crypto::{test::signer::MockSigner, Signer};
use radicle::node::{FetchResult, Handle as _};
-
use radicle::prelude::Id;
use radicle::storage::{ReadRepository, ReadStorage};
+
use radicle::test::fixtures;
use radicle::{assert_matches, rad};

use crate::service;
use crate::service::tracking::Scope;
use crate::storage::git::transport;
-
use crate::test::environment::{converge, Node};
+
use crate::test::environment::{converge, Environment, Node};
use crate::test::logger;

#[test]
@@ -462,23 +462,30 @@ fn test_fetch_up_to_date() {
fn test_concurrent_fetches() {
    logger::init(log::Level::Debug);

-
    let tmp = tempfile::tempdir().unwrap();
-
    let mut alice = Node::init(tmp.path());
-
    let mut bob = Node::init(tmp.path());
-
    let mut bob_repos: HashSet<Id> = HashSet::from_iter([
-
        bob.project("bob-1", ""),
-
        bob.project("bob-2", ""),
-
        bob.project("bob-3", ""),
-
        bob.project("bob-4", ""),
-
        bob.project("bob-5", ""),
-
    ]);
-
    let mut alice_repos: HashSet<Id> = HashSet::from_iter([
-
        alice.project("alice-1", ""),
-
        alice.project("alice-2", ""),
-
        alice.project("alice-3", ""),
-
        alice.project("alice-4", ""),
-
        alice.project("alice-5", ""),
-
    ]);
+
    let env = Environment::new();
+
    let scale = env.scale();
+
    let mut bob_repos = HashSet::new();
+
    let mut alice_repos = HashSet::new();
+
    let mut alice = Node::init(&env.tmp());
+
    let mut bob = Node::init(&env.tmp());
+

+
    for i in 0..scale.max(3) {
+
        // Create a repo for Alice.
+
        let tmp = tempfile::tempdir().unwrap();
+
        let (repo, _) = fixtures::repository(tmp.path());
+
        fixtures::populate(&repo, scale);
+

+
        let rid = alice.project_from(&format!("alice-{i}"), "", &repo);
+
        alice_repos.insert(rid);
+

+
        // Create a repo for Bob.
+
        let tmp = tempfile::tempdir().unwrap();
+
        let (repo, _) = fixtures::repository(tmp.path());
+
        fixtures::populate(&repo, scale);
+

+
        let rid = bob.project_from(&format!("bob-{i}"), "", &repo);
+
        bob_repos.insert(rid);
+
    }

    let mut alice = alice.spawn(service::Config::default());
    let mut bob = bob.spawn(service::Config::default());
modified radicle-node/src/wire/protocol.rs
@@ -409,7 +409,7 @@ where

        #[cfg(test)]
        if c.receiver.is_empty() {
-
            panic!("Wire:flush: redundant flush");
+
            panic!("Wire::flush: redundant flush");
        }

        for data in c.receiver.try_iter() {
modified radicle/src/test/fixtures.rs
@@ -79,13 +79,53 @@ pub fn repository<P: AsRef<Path>>(path: P) -> (git2::Repository, git2::Oid) {
    repo.set_head("refs/heads/master").unwrap();
    repo.checkout_head(None).unwrap();

-
    // Look, I don't really understand why we have to do this, but we do.
    drop(tree);
    drop(head);

    (repo, oid)
}

+
/// Populate a repository with commits, branches and blobs.
+
pub fn populate(repo: &git2::Repository, scale: usize) {
+
    assert!(
+
        scale <= 8,
+
        "Scale parameter must be less than or equal to 8"
+
    );
+
    if scale == 0 {
+
        return;
+
    }
+
    let head = repo.head().unwrap().peel_to_commit().unwrap();
+
    let rng = fastrand::Rng::with_seed(42);
+
    let mut buffer = vec![0; 1024 * 1024 * scale];
+

+
    for _ in 0..scale {
+
        let random = std::iter::repeat_with(|| rng.alphanumeric())
+
            .take(7)
+
            .collect::<String>()
+
            .to_lowercase();
+
        let name = format!("feature/{random}");
+
        let signature = git2::Signature::now("Radicle", "radicle@radicle.xyz").unwrap();
+

+
        rng.fill(&mut buffer);
+

+
        let blob = repo.blob(&buffer).unwrap();
+
        let mut builder = repo.treebuilder(None).unwrap();
+
        builder.insert("random.txt", blob, 0o100_644).unwrap();
+
        let tree_oid = builder.write().unwrap();
+
        let tree = repo.find_tree(tree_oid).unwrap();
+

+
        repo.commit(
+
            Some(&format!("refs/heads/{name}")),
+
            &signature,
+
            &signature,
+
            &format!("Initialize new branch feature/{random}"),
+
            &tree,
+
            &[&head],
+
        )
+
        .unwrap();
+
    }
+
}
+

/// Generate random fixtures.
pub mod gen {
    use super::*;