node: More realistic concurrent fetch test
This implements a test which can optionally use a lot more data by setting `RAD_TEST_SCALE` to a higher value.
This commit is contained in:
parent
8205f8ad4e
commit
ea4294c79f
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
Loading…
Reference in New Issue