node: Improvements to test reliability

This commit is contained in:
cloudhead 2024-03-31 22:55:02 +02:00
parent e90c6a49bc
commit 6744ffc325
No known key found for this signature in database
5 changed files with 80 additions and 29 deletions

View File

@ -12,7 +12,7 @@ use radicle::node::{Address, Alias, DEFAULT_TIMEOUT};
use radicle::prelude::RepoId; use radicle::prelude::RepoId;
use radicle::profile; use radicle::profile;
use radicle::profile::Home; use radicle::profile::Home;
use radicle::storage::{ReadStorage, RemoteRepository}; use radicle::storage::{ReadStorage, RefUpdate, RemoteRepository};
use radicle::test::fixtures; use radicle::test::fixtures;
use radicle_cli_test::TestFormula; use radicle_cli_test::TestFormula;
@ -333,7 +333,7 @@ fn rad_id() {
let events = alice.handle.events(); let events = alice.handle.events();
bob.fork(acme, bob.home.path()).unwrap(); bob.fork(acme, bob.home.path()).unwrap();
bob.announce(acme, 2, bob.home.path()).unwrap(); bob.announce(acme, 2, bob.home.path()).unwrap();
alice.has_inventory_of(&acme, &bob.id); alice.has_remote_of(&acme, &bob.id);
// Alice must have Bob to try add them as a delegate // Alice must have Bob to try add them as a delegate
events events
@ -383,12 +383,12 @@ fn rad_id_multi_delegate() {
eve.connect(&alice).converge([&alice]); eve.connect(&alice).converge([&alice]);
bob.fork(acme, working.join("bob")).unwrap(); bob.fork(acme, working.join("bob")).unwrap();
bob.has_inventory_of(&acme, &alice.id); bob.has_remote_of(&acme, &alice.id);
alice.has_inventory_of(&acme, &bob.id); alice.has_remote_of(&acme, &bob.id);
eve.fork(acme, working.join("eve")).unwrap(); eve.fork(acme, working.join("eve")).unwrap();
eve.has_inventory_of(&acme, &bob.id); eve.has_remote_of(&acme, &bob.id);
alice.has_inventory_of(&acme, &eve.id); alice.has_remote_of(&acme, &eve.id);
// TODO: Have formula with two connected nodes and a tracked project. // TODO: Have formula with two connected nodes and a tracked project.
@ -541,7 +541,7 @@ fn rad_id_conflict() {
bob.fork(acme, working.join("bob")).unwrap(); bob.fork(acme, working.join("bob")).unwrap();
bob.announce(acme, 2, bob.home.path()).unwrap(); bob.announce(acme, 2, bob.home.path()).unwrap();
alice.has_inventory_of(&acme, &bob.id); alice.has_remote_of(&acme, &bob.id);
formula(&environment.tmp(), "examples/rad-id-conflict.md") formula(&environment.tmp(), "examples/rad-id-conflict.md")
.unwrap() .unwrap()
@ -983,9 +983,9 @@ fn rad_clean() {
bob.fork(acme, bob.home.path()).unwrap(); bob.fork(acme, bob.home.path()).unwrap();
bob.announce(acme, 1, bob.home.path()).unwrap(); bob.announce(acme, 1, bob.home.path()).unwrap();
bob.has_inventory_of(&acme, &alice.id); bob.has_remote_of(&acme, &alice.id);
alice.has_inventory_of(&acme, &bob.id); alice.has_remote_of(&acme, &bob.id);
eve.has_inventory_of(&acme, &alice.id); eve.has_remote_of(&acme, &alice.id);
formula(&environment.tmp(), "examples/rad-clean.md") formula(&environment.tmp(), "examples/rad-clean.md")
.unwrap() .unwrap()
@ -1117,8 +1117,8 @@ fn rad_clone_all() {
// Fork and sync repo. // Fork and sync repo.
bob.fork(acme, bob.home.path()).unwrap(); bob.fork(acme, bob.home.path()).unwrap();
bob.announce(acme, 2, bob.home.path()).unwrap(); bob.announce(acme, 2, bob.home.path()).unwrap();
bob.has_inventory_of(&acme, &alice.id); bob.has_remote_of(&acme, &alice.id);
alice.has_inventory_of(&acme, &bob.id); alice.has_remote_of(&acme, &bob.id);
test( test(
"examples/rad-clone-all.md", "examples/rad-clone-all.md",
@ -1127,7 +1127,7 @@ fn rad_clone_all() {
[], [],
) )
.unwrap(); .unwrap();
eve.has_inventory_of(&acme, &bob.id); eve.has_remote_of(&acme, &bob.id);
} }
#[test] #[test]
@ -1509,7 +1509,14 @@ fn test_cob_replication() {
// Wait for Alice to fetch the clone refs. // Wait for Alice to fetch the clone refs.
events events
.wait( .wait(
|e| matches!(e, Event::RefsFetched { .. }).then_some(()), |e| {
matches!(
e,
Event::RefsFetched { updated, .. }
if updated.iter().any(|u| matches!(u, RefUpdate::Created { .. }))
)
.then_some(())
},
time::Duration::from_secs(6), time::Duration::from_secs(6),
) )
.unwrap(); .unwrap();
@ -1634,6 +1641,8 @@ fn rad_sync() {
bob.routes_to(&[(acme, alice.id)]); bob.routes_to(&[(acme, alice.id)]);
eve.routes_to(&[(acme, alice.id)]); eve.routes_to(&[(acme, alice.id)]);
alice.routes_to(&[(acme, alice.id), (acme, eve.id), (acme, bob.id)]); alice.routes_to(&[(acme, alice.id), (acme, eve.id), (acme, bob.id)]);
alice.is_synced_with(&acme, &eve.id);
alice.is_synced_with(&acme, &bob.id);
test( test(
"examples/rad-sync.md", "examples/rad-sync.md",
@ -1709,12 +1718,18 @@ fn test_replication_via_seed() {
seed.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]); seed.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]);
bob.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]); bob.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]);
seed_events seed_events.iter().any(|e| {
.iter() matches!(
.any(|e| matches!(e, Event::RefsFetched { remote, .. } if remote == bob.id)); e, Event::RefsFetched { updated, remote, .. }
alice_events if remote == bob.id && updated.iter().any(|u| u.is_created())
.iter() )
.any(|e| matches!(e, Event::RefsFetched { remote, .. } if remote == seed.id)); });
alice_events.iter().any(|e| {
matches!(
e, Event::RefsFetched { updated, remote, .. }
if remote == seed.id && updated.iter().any(|u| u.is_created())
)
});
seed.storage seed.storage
.repository(rid) .repository(rid)
@ -1763,13 +1778,13 @@ fn rad_remote() {
bob.routes_to(&[(rid, alice.id)]); bob.routes_to(&[(rid, alice.id)]);
bob.fork(rid, bob.home.path()).unwrap(); bob.fork(rid, bob.home.path()).unwrap();
bob.announce(rid, 2, bob.home.path()).unwrap(); bob.announce(rid, 2, bob.home.path()).unwrap();
alice.has_inventory_of(&rid, &bob.id); alice.has_remote_of(&rid, &bob.id);
eve.connect(&bob); eve.connect(&bob);
eve.routes_to(&[(rid, alice.id)]); eve.routes_to(&[(rid, alice.id)]);
eve.fork(rid, eve.home.path()).unwrap(); eve.fork(rid, eve.home.path()).unwrap();
eve.announce(rid, 2, eve.home.path()).unwrap(); eve.announce(rid, 2, eve.home.path()).unwrap();
alice.has_inventory_of(&rid, &eve.id); alice.has_remote_of(&rid, &eve.id);
test( test(
"examples/rad-remote.md", "examples/rad-remote.md",
@ -2090,7 +2105,7 @@ fn git_push_diverge() {
bob.connect(&alice).converge([&alice]); bob.connect(&alice).converge([&alice]);
bob.fork(acme, working.join("bob")).unwrap(); bob.fork(acme, working.join("bob")).unwrap();
alice.has_inventory_of(&acme, &bob.id); alice.has_remote_of(&acme, &bob.id);
formula(&environment.tmp(), "examples/git/git-push-diverge.md") formula(&environment.tmp(), "examples/git/git-push-diverge.md")
.unwrap() .unwrap()
@ -2131,7 +2146,7 @@ fn rad_push_and_pull_patches() {
bob.connect(&alice).converge([&alice]); bob.connect(&alice).converge([&alice]);
bob.fork(acme, working.join("bob")).unwrap(); bob.fork(acme, working.join("bob")).unwrap();
alice.has_inventory_of(&acme, &bob.id); alice.has_remote_of(&acme, &bob.id);
formula(&environment.tmp(), "examples/rad-push-and-pull-patches.md") formula(&environment.tmp(), "examples/rad-push-and-pull-patches.md")
.unwrap() .unwrap()

View File

@ -19,6 +19,7 @@ use radicle::identity::{RepoId, Visibility};
use radicle::node::config::ConnectAddress; use radicle::node::config::ConnectAddress;
use radicle::node::policy::store as policy; use radicle::node::policy::store as policy;
use radicle::node::routing::Store; use radicle::node::routing::Store;
use radicle::node::seed::Store as _;
use radicle::node::Database; use radicle::node::Database;
use radicle::node::{Alias, POLICIES_DB_FILE}; use radicle::node::{Alias, POLICIES_DB_FILE};
use radicle::node::{ConnectOptions, Handle as _}; use radicle::node::{ConnectOptions, Handle as _};
@ -244,6 +245,14 @@ impl<G: Signer + cyphernet::Ecdh> NodeHandle<G> {
.unwrap() .unwrap()
} }
/// Get sync status of a repo.
pub fn synced_seeds(&self, rid: &RepoId) -> Vec<node::seed::SyncedSeed> {
let db = Database::reader(self.home.node().join(node::NODE_DB_FILE)).unwrap();
let seeds = db.seeds_for(rid).unwrap();
seeds.into_iter().collect::<Result<Vec<_>, _>>().unwrap()
}
/// Wait until this node's routing table matches the remotes. /// Wait until this node's routing table matches the remotes.
pub fn converge<'a>( pub fn converge<'a>(
&'a self, &'a self,
@ -278,9 +287,23 @@ impl<G: Signer + cyphernet::Ecdh> NodeHandle<G> {
} }
} }
/// Wait until this node is synced with another node, for the given repository.
#[track_caller]
pub fn is_synced_with(&mut self, rid: &RepoId, nid: &NodeId) {
log::debug!(target: "test", "Waiting for {} to be in sync with {nid} for {rid}", self.id);
loop {
let seeds = self.handle.seeds(*rid).unwrap();
if seeds.iter().any(|s| s.nid == *nid && s.is_synced()) {
break;
}
thread::sleep(Duration::from_millis(100));
}
}
/// Wait until this node has the inventory of another node. /// Wait until this node has the inventory of another node.
#[track_caller] #[track_caller]
pub fn has_inventory_of(&self, rid: &RepoId, nid: &NodeId) { pub fn has_remote_of(&self, rid: &RepoId, nid: &NodeId) {
log::debug!(target: "test", "Waiting for {} to have {rid}/{nid}", self.id); log::debug!(target: "test", "Waiting for {} to have {rid}/{nid}", self.id);
let events = self.handle.events(); let events = self.handle.events();

View File

@ -1559,7 +1559,7 @@ fn test_refs_synced_event() {
} }
#[test] #[test]
fn test_push_and_pull() { fn test_init_and_seed() {
let tempdir = tempfile::tempdir().unwrap(); let tempdir = tempfile::tempdir().unwrap();
let storage_alice = Storage::open( let storage_alice = Storage::open(
@ -1626,19 +1626,22 @@ fn test_push_and_pull() {
assert!(bob.get(proj_id).unwrap().is_none()); assert!(bob.get(proj_id).unwrap().is_none());
// Bob seeds Alice's project. // Bob seeds Alice's project.
let (sender, _) = chan::bounded(1); let (sender, receiver) = chan::bounded(1);
bob.command(service::Command::Seed( bob.command(service::Command::Seed(
proj_id, proj_id,
policy::Scope::default(), policy::Scope::default(),
sender, sender,
)); ));
assert!(receiver.recv().unwrap());
// Eve seeds Alice's project. // Eve seeds Alice's project.
let (sender, _) = chan::bounded(1); let (sender, receiver) = chan::bounded(1);
eve.command(service::Command::Seed( eve.command(service::Command::Seed(
proj_id, proj_id,
policy::Scope::default(), policy::Scope::default(),
sender, sender,
)); ));
assert!(receiver.recv().unwrap());
let (send, _) = chan::bounded(1); let (send, _) = chan::bounded(1);
// Alice announces her inventory. // Alice announces her inventory.

View File

@ -229,6 +229,16 @@ impl RefUpdate {
RefUpdate::Skipped { name, .. } => name.as_refstr(), RefUpdate::Skipped { name, .. } => name.as_refstr(),
} }
} }
/// Is it an update.
pub fn is_updated(&self) -> bool {
matches!(self, RefUpdate::Updated { .. })
}
/// Is it a create.
pub fn is_created(&self) -> bool {
matches!(self, RefUpdate::Created { .. })
}
} }
impl fmt::Display for RefUpdate { impl fmt::Display for RefUpdate {

View File

@ -59,11 +59,11 @@ pub fn fetch<W: WriteRepository>(
repo.set_identity_head()?; repo.set_identity_head()?;
repo.set_head()?; repo.set_head()?;
let validations = repo.validate()?; let validations = repo.validate()?;
if !validations.is_empty() { if !validations.is_empty() {
return Err(crate::storage::FetchError::Validation { validations }); return Err(crate::storage::FetchError::Validation { validations });
} }
Ok(updates) Ok(updates)
} }