From 5a840983a8ba27f4fafbfeb82d15a10b1b9c7842 Mon Sep 17 00:00:00 2001 From: Lorenz Leutgeb Date: Tue, 14 May 2024 18:19:23 +0200 Subject: [PATCH] node, cli: Refactor test environment --- crates/radicle-cli/tests/commands.rs | 1537 +++++------------- crates/radicle-cli/tests/util/environment.rs | 316 ++++ crates/radicle-cli/tests/util/formula.rs | 39 + crates/radicle-cli/tests/util/mod.rs | 2 + crates/radicle-node/src/test.rs | 2 +- crates/radicle-node/src/test/node.rs | 532 ++++++ crates/radicle-node/src/tests/e2e.rs | 30 +- 7 files changed, 1353 insertions(+), 1105 deletions(-) create mode 100644 crates/radicle-cli/tests/util/environment.rs create mode 100644 crates/radicle-cli/tests/util/formula.rs create mode 100644 crates/radicle-cli/tests/util/mod.rs create mode 100644 crates/radicle-node/src/test/node.rs diff --git a/crates/radicle-cli/tests/commands.rs b/crates/radicle-cli/tests/commands.rs index ee319594..e629f254 100644 --- a/crates/radicle-cli/tests/commands.rs +++ b/crates/radicle-cli/tests/commands.rs @@ -8,83 +8,27 @@ use radicle::node::address::Store as _; use radicle::node::config::seeds::RADICLE_NODE_BOOTSTRAP_IRIS; use radicle::node::config::DefaultSeedingPolicy; use radicle::node::routing::Store as _; -use radicle::node::Handle as _; use radicle::node::UserAgent; -use radicle::node::{Address, Alias, DEFAULT_TIMEOUT}; +use radicle::node::{Address, Alias, Config, Handle as _, DEFAULT_TIMEOUT}; use radicle::prelude::{NodeId, RepoId}; use radicle::profile; -use radicle::profile::{env, Home}; +use radicle::profile::Home; use radicle::storage::{ReadStorage, RefUpdate, RemoteRepository}; use radicle::test::fixtures; -use radicle_cli_test::TestFormula; use radicle_node::service::policy::Scope; use radicle_node::service::Event; -use radicle_node::test::environment::{Config, Environment, Node}; #[allow(unused_imports)] use radicle_node::test::logger; +use radicle_node::test::node::Node; use radicle_node::PROTOCOL_VERSION; -/// Seed used in tests. -const RAD_SEED: &str = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; - -mod config { - use super::*; - use radicle::node::config::{Config, Limits, Network, RateLimit, RateLimits}; - use radicle::profile; - - /// Configuration for a test seed node. - /// - /// It sets the `RateLimit::capacity` to `usize::MAX` ensuring - /// that there are no rate limits for test nodes, since they all - /// operate on the same IP address. This prevents any announcement - /// messages from being dropped. - pub fn seed(alias: &'static str) -> Config { - Config { - network: Network::Test, - relay: node::config::Relay::Always, - limits: Limits { - rate: RateLimits { - inbound: RateLimit { - fill_rate: 1.0, - capacity: usize::MAX, - }, - outbound: RateLimit { - fill_rate: 1.0, - capacity: usize::MAX, - }, - }, - ..Limits::default() - }, - external_addresses: vec![node::Address::from_str(&format!( - "{alias}.radicle.example:8776" - )) - .unwrap()], - ..node(alias) - } - } - - /// Relay node config. - pub fn relay(alias: &'static str) -> Config { - Config { - relay: node::config::Relay::Always, - ..node(alias) - } - } - - /// Test node config. - pub fn node(alias: &'static str) -> Config { - Config::test(Alias::new(alias)) - } - - /// Test profile config. - pub fn profile(alias: &'static str) -> profile::Config { - Environment::config(Alias::new(alias)) - } -} +mod util; +use util::environment::{config, Environment}; +use util::formula::formula; /// Run a CLI test file. -fn test<'a>( +pub(crate) fn test<'a>( test: impl AsRef, cwd: impl AsRef, home: Option<&Home>, @@ -105,35 +49,6 @@ fn test<'a>( Ok(()) } -fn formula(root: &Path, test: impl AsRef) -> Result> { - let mut formula = TestFormula::new(root.to_path_buf()); - let base = Path::new(env!("CARGO_MANIFEST_DIR")); - - formula - .env("GIT_AUTHOR_DATE", "1671125284") - .env("GIT_AUTHOR_EMAIL", "radicle@localhost") - .env("GIT_AUTHOR_NAME", "radicle") - .env("GIT_COMMITTER_DATE", "1671125284") - .env("GIT_COMMITTER_EMAIL", "radicle@localhost") - .env("GIT_COMMITTER_NAME", "radicle") - .env("EDITOR", "true") - .env("TZ", "UTC") - .env("LANG", "C") - .env("USER", "alice") - .env(env::RAD_PASSPHRASE, "radicle") - .env(env::RAD_KEYGEN_SEED, RAD_SEED) - .env(env::RAD_RNG_SEED, "0") - .env(env::RAD_LOCAL_TIME, "1671125284") - .envs(git::env::GIT_DEFAULT_CONFIG) - .build(&[ - ("radicle-remote-helper", "git-remote-rad"), - ("radicle-cli", "rad"), - ]) - .file(base.join(test))?; - - Ok(formula) -} - #[test] fn rad_auth() { test("examples/rad-auth.md", Path::new("."), None, []).unwrap(); @@ -146,42 +61,19 @@ fn rad_auth_errors() { #[test] fn rad_issue() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let home = &profile.home; - let working = environment.tmp().join("working"); - - // Setup a test repository. - fixtures::repository(&working); - - test("examples/rad-init.md", &working, Some(home), []).unwrap(); - test("examples/rad-issue.md", &working, Some(home), []).unwrap(); + Environment::alice(["rad-init", "rad-issue"]); } #[test] fn rad_cob_update() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = environment.tmp().join("working"); - let home = &profile.home; - - let base = Path::new(env!("CARGO_MANIFEST_DIR")); - - std::fs::create_dir_all(base).unwrap(); - std::fs::create_dir_all(working.clone()).unwrap(); - - // Setup a test repository. - fixtures::repository(&working); - - test("examples/rad-init.md", &working, Some(home), []).unwrap(); - test("examples/rad-cob-update.md", &working, Some(home), []).unwrap(); + Environment::alice(["rad-init", "rad-cob-log"]); } #[test] fn rad_cob_update_identity() { let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = environment.tmp().join("working"); + let profile = environment.profile("alice"); + let working = environment.tempdir().join("working"); let home = &profile.home; let base = Path::new(env!("CARGO_MANIFEST_DIR")); @@ -222,9 +114,9 @@ fn rad_cob_multiset() { } let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); + let profile = environment.profile("alice"); let home = &profile.home; - let working = environment.tmp().join("working"); + let working = environment.tempdir().join("working"); let base = Path::new(env!("CARGO_MANIFEST_DIR")); std::fs::create_dir_all(base).unwrap(); @@ -246,73 +138,42 @@ fn rad_cob_multiset() { #[test] fn rad_cob_log() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let home = &profile.home; - let working = environment.tmp().join("working"); - - // Setup a test repository. - fixtures::repository(&working); - - test("examples/rad-init.md", &working, Some(home), []).unwrap(); - test("examples/rad-cob-log.md", &working, Some(home), []).unwrap(); + Environment::alice(["rad-init", "rad-cob-log"]); } #[test] fn rad_cob_show() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let home = &profile.home; - let working = environment.tmp().join("working"); - - // Setup a test repository. - fixtures::repository(&working); - - test("examples/rad-init.md", &working, Some(home), []).unwrap(); - test("examples/rad-cob-show.md", &working, Some(home), []).unwrap(); + Environment::alice(["rad-init", "rad-cob-show"]); } #[test] fn rad_cob_migrate() { let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); + let profile = environment.profile("alice"); let home = &profile.home; - let working = environment.tmp().join("working"); home.cobs_db_mut() .unwrap() .raw_query(|conn| conn.execute("PRAGMA user_version = 0")) .unwrap(); - // Setup a test repository. - fixtures::repository(&working); + environment.repository(&profile); - test("examples/rad-init.md", &working, Some(home), []).unwrap(); - test("examples/rad-cob-migrate.md", &working, Some(home), []).unwrap(); + environment + .tests(["rad-init", "rad-cob-migrate"], &profile) + .unwrap(); } #[test] +#[ignore = "part of many other tests"] fn rad_init() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - - // Setup a test repository. - fixtures::repository(working.path()); - - test( - "examples/rad-init.md", - working.path(), - Some(&profile.home), - [], - ) - .unwrap(); + Environment::alice(["rad-init"]); } #[test] fn rad_init_existing() { let mut environment = Environment::new(); - let mut profile = environment.node(config::node("alice")); + let mut profile = environment.node("alice"); let working = tempfile::tempdir().unwrap(); let rid = profile.project("heartwood", "Radicle Heartwood Protocol & Stack"); @@ -333,87 +194,50 @@ fn rad_init_existing() { #[test] fn rad_init_no_seed() { - let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); - - fixtures::repository(working.join("alice")); - - test( - "examples/rad-init-no-seed.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + Environment::alice(["rad-init-no-seed"]); } #[test] fn rad_init_with_existing_remote() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - - // Setup a test repository. - fixtures::repository(working.path()); - - test( - "examples/rad-init-with-existing-remote.md", - working.path(), - Some(&profile.home), - [], - ) - .unwrap(); + Environment::alice(["rad-init-with-existing-remote"]); } #[test] fn rad_init_no_git() { let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); + let profile = environment.profile("alice"); - test( - "examples/rad-init-no-git.md", - working.path(), - Some(&profile.home), - [], - ) - .unwrap(); + // NOTE: There is no repository set up here. + + environment.test("rad-init-no-git", &profile).unwrap(); } #[test] fn rad_inspect() { let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); + let profile = environment.profile("alice"); - // Setup a test repository. - fixtures::repository(working.path()); + environment.repository(&profile); + environment + .tests(["rad-init", "rad-inspect"], &profile) + .unwrap(); + + // NOTE: The next test runs without $RAD_HOME set. test( - "examples/rad-init.md", - working.path(), - Some(&profile.home), + "examples/rad-inspect-noauth.md", + environment.work(&profile), + None, [], ) .unwrap(); - - test( - "examples/rad-inspect.md", - working.path(), - Some(&profile.home), - [], - ) - .unwrap(); - - test("examples/rad-inspect-noauth.md", working.path(), None, []).unwrap(); } #[test] fn rad_config() { let mut environment = Environment::new(); let alias = Alias::new("alice"); - let profile = environment.profile(profile::Config { + let profile = environment.profile_with(profile::Config { preferred_seeds: vec![RADICLE_NODE_BOOTSTRAP_IRIS.clone()], ..profile::Config::new(alias) }); @@ -430,37 +254,18 @@ fn rad_config() { #[test] fn rad_warn_old_nodes() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - - test( - "examples/rad-warn-old-nodes.md", - working.path(), - Some(&profile.home), - [], - ) - .unwrap(); + Environment::alice(["rad-warn-old-nodes"]); } #[test] fn rad_checkout() { let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); + let profile = environment.profile("alice"); let copy = tempfile::tempdir().unwrap(); - // Setup a test repository. - fixtures::repository(working.path()); - - test( - "examples/rad-init.md", - working.path(), - Some(&profile.home), - [], - ) - .unwrap(); + environment.repository(&profile); + environment.test("rad-init", &profile).unwrap(); test( "examples/rad-checkout.md", copy.path(), @@ -491,18 +296,15 @@ fn rad_checkout() { #[test] fn rad_id() { let mut environment = Environment::new(); - let alice = environment.node(config::node("alice")); - let bob = environment.node(config::node("bob")); - let working = tempfile::tempdir().unwrap(); - let working = working.path(); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - // Setup a test repository. - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -529,7 +331,7 @@ fn rad_id() { test( "examples/rad-id.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -539,19 +341,16 @@ fn rad_id() { #[test] fn rad_id_threshold() { let mut environment = Environment::new(); - let alice = environment.node(config::node("alice")); - let bob = environment.node(config::node("bob")); - let seed = environment.node(config::node("seed")); - let working = tempfile::tempdir().unwrap(); - let working = working.path(); + let alice = environment.node("alice"); + let bob = environment.node("bob"); + let seed = environment.node("seed"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - // Setup a test repository. - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -573,21 +372,21 @@ fn rad_id_threshold() { alice.routes_to(&[(acme, seed.id)]); seed.handle.fetch(acme, alice.id, DEFAULT_TIMEOUT).unwrap(); - formula(&environment.tmp(), "examples/rad-id-threshold.md") + formula(&environment.tempdir(), "examples/rad-id-threshold.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob"), + environment.work(&bob), [("RAD_HOME", bob.home.path().display())], ) .home( "seed", - working.join("seed"), + environment.work(&seed), [("RAD_HOME", seed.home.path().display())], ) .run() @@ -597,18 +396,15 @@ fn rad_id_threshold() { #[test] fn rad_id_threshold_soft_fork() { let mut environment = Environment::new(); - let alice = environment.node(config::node("alice")); - let bob = environment.node(config::node("bob")); - let working = tempfile::tempdir().unwrap(); - let working = working.path(); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - // Setup a test repository. - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -628,65 +424,43 @@ fn rad_id_threshold_soft_fork() { ) .unwrap(); - formula(&environment.tmp(), "examples/rad-id-threshold-soft-fork.md") - .unwrap() - .home( - "alice", - working.join("alice"), - [("RAD_HOME", alice.home.path().display())], - ) - .home( - "bob", - working.join("bob"), - [("RAD_HOME", bob.home.path().display())], - ) - .run() - .unwrap(); + formula( + &environment.tempdir(), + "examples/rad-id-threshold-soft-fork.md", + ) + .unwrap() + .home( + "alice", + environment.work(&alice), + [("RAD_HOME", alice.home.path().display())], + ) + .home( + "bob", + environment.work(&bob), + [("RAD_HOME", bob.home.path().display())], + ) + .run() + .unwrap(); } #[test] fn rad_id_update_delete_field() { - let mut environment = Environment::new(); - let alice = environment.node(config::node("alice")); - let working = tempfile::tempdir().unwrap(); - let working = working.path(); - - // Setup a test repository. - fixtures::repository(working.join("alice")); - - test( - "examples/rad-init.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); - - test( - "examples/rad-id-update-delete-field.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + Environment::alice(["rad-init", "rad-id-update-delete-field"]); } #[test] fn rad_id_multi_delegate() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let eve = environment.node(Config::test(Alias::new("eve"))); - let working = tempfile::tempdir().unwrap(); - let working = working.path(); + let alice = environment.node("alice"); + let bob = environment.node("bob"); + let eve = environment.node("eve"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - // Setup a test repository. - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -701,28 +475,27 @@ fn rad_id_multi_delegate() { alice.connect(&bob).converge([&bob]); eve.connect(&alice).converge([&alice]); - bob.fork(acme, working.join("bob")).unwrap(); + bob.fork(acme, environment.work(&bob)).unwrap(); bob.has_remote_of(&acme, &alice.id); alice.has_remote_of(&acme, &bob.id); - eve.fork(acme, working.join("eve")).unwrap(); + eve.fork(acme, environment.work(&eve)).unwrap(); eve.has_remote_of(&acme, &bob.id); alice.has_remote_of(&acme, &eve.id); alice.is_synced_with(&acme, &eve.id); alice.is_synced_with(&acme, &bob.id); // TODO: Have formula with two connected nodes and a tracked project. - - formula(&environment.tmp(), "examples/rad-id-multi-delegate.md") + formula(&environment.tempdir(), "examples/rad-id-multi-delegate.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob"), + environment.work(&bob), [("RAD_HOME", bob.home.path().display())], ) .run() @@ -733,21 +506,18 @@ fn rad_id_multi_delegate() { #[ignore = "slow"] fn rad_id_collaboration() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let eve = environment.node(Config::test(Alias::new("eve"))); - let seed = environment.node(config::seed("seed")); - let distrustful = environment.node(config::seed("distrustful")); - let working = tempfile::tempdir().unwrap(); - let working = working.path(); + let alice = environment.node("alice"); + let bob = environment.node("bob"); + let eve = environment.node("eve"); + let seed = environment.seed("seed"); + let distrustful = environment.seed("distrustful"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - // Setup a test repository. - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -791,21 +561,21 @@ fn rad_id_collaboration() { .fetch(acme, alice.id, DEFAULT_TIMEOUT) .unwrap(); - formula(&environment.tmp(), "examples/rad-id-collaboration.md") + formula(&environment.tempdir(), "examples/rad-id-collaboration.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob"), + environment.work(&bob), [("RAD_HOME", bob.home.path().display())], ) .home( "eve", - working.join("eve"), + environment.work(&eve), [("RAD_HOME", eve.home.path().display())], ) .run() @@ -838,18 +608,15 @@ fn rad_id_collaboration() { #[test] fn rad_id_conflict() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = tempfile::tempdir().unwrap(); - let working = working.path(); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - // Setup a test repository. - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -860,20 +627,20 @@ fn rad_id_conflict() { alice.connect(&bob).converge([&bob]); - bob.fork(acme, working.join("bob")).unwrap(); + bob.fork(acme, environment.work(&bob)).unwrap(); bob.announce(acme, 2, bob.home.path()).unwrap(); alice.has_remote_of(&acme, &bob.id); - formula(&environment.tmp(), "examples/rad-id-conflict.md") + formula(&environment.tempdir(), "examples/rad-id-conflict.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob"), + environment.work(&bob), [("RAD_HOME", bob.home.path().display())], ) .run() @@ -883,61 +650,25 @@ fn rad_id_conflict() { #[test] fn rad_id_unknown_field() { let mut environment = Environment::new(); - let alice = environment.node(config::node("alice")); - let working = tempfile::tempdir().unwrap(); - let working = working.path(); + let alice = environment.node("alice"); - // Setup a test repository. - fixtures::repository(working.join("alice")); - - test( - "examples/rad-init.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.repository(&alice); + environment.test("rad-init", &alice).unwrap(); let alice = alice.spawn(); - - test( - "examples/rad-id-unknown-field.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-id-unknown-field", &alice).unwrap(); } #[test] fn rad_id_private() { - let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); - - fixtures::repository(working.join("alice")); - - test( - "examples/rad-init-private.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); - test( - "examples/rad-id-private.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + Environment::alice(["rad-init-private", "rad-id-private"]); } #[test] fn rad_node_connect() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let working = tempfile::tempdir().unwrap(); let alice = alice.spawn(); let bob = bob.spawn(); @@ -962,8 +693,8 @@ fn rad_node_connect() { fn rad_node_connect_without_address() { logger::init(log::Level::Debug); let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); let working = tempfile::tempdir().unwrap(); let bob = bob.spawn(); @@ -1004,7 +735,7 @@ fn rad_node_connect_without_address() { #[test] fn rad_node() { let mut environment = Environment::new(); - let alice = environment.node(Config { + let alice = environment.node_with(Config { external_addresses: vec![ Address::from(net::SocketAddr::from(([41, 12, 98, 112], 8776))), Address::from_str("seed.cloudhead.io:8776").unwrap(), @@ -1036,108 +767,45 @@ fn rad_node() { #[test] fn rad_patch() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test("examples/rad-issue.md", working.path(), Some(home), []).unwrap(); - test("examples/rad-patch.md", working.path(), Some(home), []).unwrap(); + Environment::alice(["rad-init", "rad-issue", "rad-patch"]); } #[test] fn rad_patch_diff() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test("examples/rad-patch-diff.md", working.path(), Some(home), []).unwrap(); + Environment::alice(["rad-init", "rad-patch-diff"]); } #[test] fn rad_patch_edit() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test("examples/rad-patch-edit.md", working.path(), Some(home), []).unwrap(); + Environment::alice(["rad-init", "rad-patch-edit"]); } #[test] fn rad_patch_checkout() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-patch-checkout.md", - working.path(), - Some(home), - [], - ) - .unwrap(); + Environment::alice(["rad-init", "rad-patch-checkout"]); } #[test] fn rad_patch_checkout_revision() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-patch-checkout.md", - working.path(), - Some(home), - [], - ) - .unwrap(); - test( - "examples/rad-patch-checkout-revision.md", - working.path(), - Some(home), - [], - ) - .unwrap(); + Environment::alice([ + "rad-init", + "rad-patch-checkout", + "rad-patch-checkout-revision", + ]); } #[test] fn rad_patch_checkout_force() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - // Setup a test repository. - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -1149,227 +817,106 @@ fn rad_patch_checkout_force() { bob.handle.seed(acme, Scope::All).unwrap(); alice.connect(&bob).converge([&bob]); - bob.rad("clone", &[acme.to_string().as_str()], working.join("bob")) - .unwrap(); + bob.rad( + "clone", + &[acme.to_string().as_str()], + environment.work(&bob), + ) + .unwrap(); - formula(&environment.tmp(), "examples/rad-patch-checkout-force.md") - .unwrap() - .home( - "alice", - working.join("alice"), - [("RAD_HOME", alice.home.path().display())], - ) - .home( - "bob", - working.join("bob"), - [("RAD_HOME", bob.home.path().display())], - ) - .run() - .unwrap(); + formula( + &environment.tempdir(), + "examples/rad-patch-checkout-force.md", + ) + .unwrap() + .home( + "alice", + environment.work(&alice), + [("RAD_HOME", alice.home.path().display())], + ) + .home( + "bob", + environment.work(&bob), + [("RAD_HOME", bob.home.path().display())], + ) + .run() + .unwrap(); } #[test] fn rad_patch_update() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-patch-update.md", - working.path(), - Some(home), - [], - ) - .unwrap(); + Environment::alice(["rad-init", "rad-patch-update"]); } #[test] #[cfg(not(target_os = "macos"))] fn rad_patch_ahead_behind() { - use std::fs; - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; + let profile = environment.profile("alice"); - // Setup a test repository. - fixtures::repository(working.path()); + environment.repository(&profile); - fs::write(working.path().join("CONTRIBUTORS"), "Alice Jones\n").unwrap(); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-patch-ahead-behind.md", - working.path(), - Some(home), - [], + std::fs::write( + environment.work(&profile).join("CONTRIBUTORS"), + "Alice Jones\n", ) .unwrap(); + + environment + .tests(["rad-init", "rad-patch-ahead-behind"], &profile) + .unwrap(); } #[test] fn rad_patch_change_base() { logger::init(log::Level::Debug); - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-patch-change-base.md", - working.path(), - Some(home), - [], - ) - .unwrap(); + Environment::alice(["rad-init", "rad-patch-change-base"]); } #[test] fn rad_patch_draft() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-patch-draft.md", - working.path(), - Some(home), - [], - ) - .unwrap(); + Environment::alice(["rad-init", "rad-patch-draft"]); } #[test] fn rad_patch_via_push() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-patch-via-push.md", - working.path(), - Some(home), - [], - ) - .unwrap(); + Environment::alice(["rad-init", "rad-patch-via-push"]); } #[test] fn rad_patch_detached_head() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-patch-detached-head.md", - working.path(), - Some(home), - [], - ) - .unwrap(); + Environment::alice(["rad-init", "rad-patch-detached-head"]); } #[test] fn rad_patch_merge_draft() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-patch-merge-draft.md", - working.path(), - Some(home), - [], - ) - .unwrap(); + Environment::alice(["rad-init", "rad-patch-merge-draft"]); } #[test] fn rad_patch_revert_merge() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-patch-revert-merge.md", - working.path(), - Some(home), - [], - ) - .unwrap(); + Environment::alice(["rad-init", "rad-patch-revert-merge"]); } #[test] #[cfg(not(target_os = "macos"))] fn rad_review_by_hunk() { - let mut environment = Environment::new(); - let profile = environment.profile(config::profile("alice")); - let working = tempfile::tempdir().unwrap(); - let home = &profile.home; - - // Setup a test repository. - fixtures::repository(working.path()); - - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test( - "examples/rad-review-by-hunk.md", - working.path(), - Some(home), - [], - ) - .unwrap(); + Environment::alice(["rad-init", "rad-review-by-hunk"]); } #[test] fn rad_patch_delete() { let mut environment = Environment::new(); - let alice = environment.node(config::relay("alice")); - let bob = environment.node(config::relay("bob")); - let seed = environment.node(config::relay("seed")); - let working = environment.tmp().join("working"); + let alice = environment.relay("alice"); + let bob = environment.relay("bob"); + let seed = environment.relay("seed"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - // Setup a test repository. - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -1385,24 +932,28 @@ fn rad_patch_delete() { bob.connect(&seed).converge([&seed]); bob.routes_to(&[(acme, seed.id)]); - bob.rad("clone", &[acme.to_string().as_str()], working.join("bob")) - .unwrap(); + bob.rad( + "clone", + &[acme.to_string().as_str()], + environment.work(&bob), + ) + .unwrap(); - formula(&environment.tmp(), "examples/rad-patch-delete.md") + formula(&environment.tempdir(), "examples/rad-patch-delete.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob"), + environment.work(&bob), [("RAD_HOME", bob.home.path().display())], ) .home( "seed", - working.join("seed"), + environment.work(&seed), [("RAD_HOME", seed.home.path().display())], ) .run() @@ -1412,10 +963,10 @@ fn rad_patch_delete() { #[test] fn rad_clean() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let eve = environment.node(Config::test(Alias::new("eve"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); + let eve = environment.node("eve"); + let working = environment.tempdir().join("working"); // Setup a test project. let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); @@ -1445,7 +996,7 @@ fn rad_clean() { alice.has_remote_of(&acme, &bob.id); eve.has_remote_of(&acme, &alice.id); - formula(&environment.tmp(), "examples/rad-clean.md") + formula(&environment.tempdir(), "examples/rad-clean.md") .unwrap() .home( "alice", @@ -1468,29 +1019,17 @@ fn rad_clean() { #[test] fn rad_seed_and_follow() { - let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = tempfile::tempdir().unwrap(); - let alice = alice.spawn(); - - test( - "examples/rad-seed-and-follow.md", - working.path(), - Some(&alice.home), - [], - ) - .unwrap(); + Environment::alice(["rad-seed-and-follow"]); } #[test] fn rad_seed_many() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let mut bob = environment.node(Config::test(Alias::new("bob"))); + let alice = environment.node("alice"); + let mut bob = environment.node("bob"); // Bob creates two projects that Alice seeds in the test let _ = bob.project("heartwood", "Radicle Heartwood Protocol & Stack"); let _ = bob.project("nixpkgs", "Home for Nix Packages"); - let working = tempfile::tempdir().unwrap(); let alice = alice.spawn(); let mut bob = bob.spawn(); @@ -1498,7 +1037,7 @@ fn rad_seed_many() { test( "examples/rad-seed-many.md", - working.path(), + environment.work(&alice), Some(&alice.home), [], ) @@ -1508,7 +1047,7 @@ fn rad_seed_many() { #[test] fn rad_unseed() { let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); + let mut alice = environment.node("alice"); let working = tempfile::tempdir().unwrap(); // Setup a test project. @@ -1521,8 +1060,7 @@ fn rad_unseed() { #[test] fn rad_unseed_many() { let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let working = tempfile::tempdir().unwrap(); + let mut alice = environment.node("alice"); // Setup a test project. alice.project("heartwood", "Radicle Heartwood Protocol & Stack"); @@ -1531,7 +1069,7 @@ fn rad_unseed_many() { test( "examples/rad-unseed-many.md", - working, + environment.work(&alice), Some(&alice.home), [], ) @@ -1541,7 +1079,7 @@ fn rad_unseed_many() { #[test] fn rad_block() { let mut environment = Environment::new(); - let alice = environment.node(Config { + let alice = environment.node_with(Config { seeding_policy: DefaultSeedingPolicy::permissive(), ..Config::test(Alias::new("alice")) }); @@ -1553,9 +1091,9 @@ fn rad_block() { #[test] fn rad_clone() { let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); + let working = environment.tempdir().join("working"); // Setup a test project. let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack"); @@ -1573,9 +1111,9 @@ fn rad_clone() { #[test] fn rad_clone_directory() { let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); + let working = environment.tempdir().join("working"); // Setup a test project. let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack"); @@ -1599,10 +1137,9 @@ fn rad_clone_directory() { #[test] fn rad_clone_all() { let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let eve = environment.node(Config::test(Alias::new("eve"))); - let working = environment.tmp().join("working"); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); + let eve = environment.node("eve"); // Setup a test project. let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack"); @@ -1623,7 +1160,7 @@ fn rad_clone_all() { test( "examples/rad-clone-all.md", - working.join("eve"), + environment.work(&eve), Some(&eve.home), [], ) @@ -1634,10 +1171,9 @@ fn rad_clone_all() { #[test] fn rad_clone_partial_fail() { let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let mut eve = environment.node(Config::test(Alias::new("eve"))); - let working = environment.tmp().join("working"); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); + let mut eve = environment.node("eve"); let carol = NodeId::from_str("z6MksFqXN3Yhqk8pTJdUGLwBTkRfQvwZXPqR2qMEhbS9wzpT").unwrap(); logger::init(log::Level::Debug); @@ -1686,7 +1222,7 @@ fn rad_clone_partial_fail() { test( "examples/rad-clone-partial-fail.md", - working.join("eve"), + environment.work(&eve), Some(&eve.home), [], ) @@ -1696,10 +1232,10 @@ fn rad_clone_partial_fail() { #[test] fn rad_clone_connect() { let mut environment = Environment::new(); - let working = environment.tmp().join("working"); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let mut eve = environment.node(Config::test(Alias::new("eve"))); + let working = environment.tempdir().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); + let mut eve = environment.node("eve"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); let ua = UserAgent::default(); let now = localtime::LocalTime::now().into(); @@ -1781,14 +1317,14 @@ fn rad_clone_connect() { #[test] fn rad_sync_without_node() { let mut environment = Environment::new(); - let alice = environment.node(config::seed("alice")); - let bob = environment.node(config::seed("bob")); - let mut eve = environment.node(config::seed("eve")); + let alice = environment.seed("alice"); + let bob = environment.seed("bob"); + let mut eve = environment.seed("eve"); let rid = RepoId::from_urn("rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5").unwrap(); eve.policies.seed(&rid, Scope::All).unwrap(); - formula(&environment.tmp(), "examples/rad-sync-without-node.md") + formula(&environment.tempdir(), "examples/rad-sync-without-node.md") .unwrap() .home( "alice", @@ -1812,11 +1348,11 @@ fn rad_sync_without_node() { #[test] fn rad_self() { let mut environment = Environment::new(); - let alice = environment.node(Config { + let alice = environment.node_with(Config { external_addresses: vec!["seed.alice.acme:8776".parse().unwrap()], ..Config::test(Alias::new("alice")) }); - let working = environment.tmp().join("working"); + let working = environment.tempdir().join("working"); test("examples/rad-self.md", working, Some(&alice.home), []).unwrap(); } @@ -1824,8 +1360,8 @@ fn rad_self() { #[test] fn rad_clone_unknown() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let working = environment.tempdir().join("working"); let alice = alice.spawn(); @@ -1841,7 +1377,7 @@ fn rad_clone_unknown() { #[test] fn rad_init_sync_not_connected() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); + let alice = environment.node("alice"); let working = tempfile::tempdir().unwrap(); let alice = alice.spawn(); @@ -1860,28 +1396,27 @@ fn rad_init_sync_not_connected() { fn rad_init_sync_preferred() { let mut environment = Environment::new(); let mut alice = environment - .node(Config { + .node_with(Config { seeding_policy: DefaultSeedingPolicy::permissive(), ..Config::test(Alias::new("alice")) }) .spawn(); - let bob = environment.profile(profile::Config { + let bob = environment.profile_with(profile::Config { preferred_seeds: vec![alice.address()], - ..config::profile("bob") + ..environment.config("bob") }); let mut bob = Node::new(bob).spawn(); - let working = environment.tmp().join("working"); bob.connect(&alice); alice.handle.follow(bob.id, None).unwrap(); - fixtures::repository(working.join("bob")); + environment.repository(&bob); // Bob initializes a repo after her node has started, and after bob has connected to it. test( "examples/rad-init-sync-preferred.md", - working.join("bob"), + environment.work(&bob), Some(&bob.home), [], ) @@ -1892,28 +1427,27 @@ fn rad_init_sync_preferred() { fn rad_init_sync_timeout() { let mut environment = Environment::new(); let mut alice = environment - .node(Config { + .node_with(Config { seeding_policy: DefaultSeedingPolicy::Block, ..Config::test(Alias::new("alice")) }) .spawn(); - let bob = environment.profile(profile::Config { + let bob = environment.profile_with(profile::Config { preferred_seeds: vec![alice.address()], - ..config::profile("bob") + ..environment.config("bob") }); let mut bob = Node::new(bob).spawn(); - let working = environment.tmp().join("working"); bob.connect(&alice); alice.handle.follow(bob.id, None).unwrap(); - fixtures::repository(working.join("bob")); + environment.repository(&bob); // Bob initializes a repo after her node has started, and after bob has connected to it. test( "examples/rad-init-sync-timeout.md", - working.join("bob"), + environment.work(&bob), Some(&bob.home), [], ) @@ -1923,21 +1457,20 @@ fn rad_init_sync_timeout() { #[test] fn rad_init_sync_and_clone() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let alice = alice.spawn(); let mut bob = bob.spawn(); bob.connect(&alice); - fixtures::repository(working.join("alice")); + environment.repository(&alice); // Alice initializes a repo after her node has started, and after bob has connected to it. test( "examples/rad-init-sync.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -1948,7 +1481,7 @@ fn rad_init_sync_and_clone() { test( "examples/rad-clone.md", - working.join("bob"), + environment.work(&bob), Some(&bob.home), [], ) @@ -1958,93 +1491,60 @@ fn rad_init_sync_and_clone() { #[test] fn rad_fetch() { let mut environment = Environment::new(); - let working = environment.tmp().join("working"); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let mut alice = alice.spawn(); let bob = bob.spawn(); alice.connect(&bob); - fixtures::repository(working.join("alice")); + environment.repository(&alice); // Alice initializes a repo after her node has started, and after bob has connected to it. - test( - "examples/rad-init-sync.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-init-sync", &alice).unwrap(); // Wait for bob to get any updates to the routing table. bob.converge([&alice]); - test( - "examples/rad-fetch.md", - working.join("bob"), - Some(&bob.home), - [], - ) - .unwrap(); + environment.test("rad-fetch", &bob).unwrap(); } #[test] fn rad_fork() { let mut environment = Environment::new(); - let working = environment.tmp().join("working"); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let mut alice = alice.spawn(); let bob = bob.spawn(); alice.connect(&bob); - fixtures::repository(working.join("alice")); + environment.repository(&alice); // Alice initializes a repo after her node has started, and after bob has connected to it. - test( - "examples/rad-init-sync.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-init-sync", &alice).unwrap(); // Wait for bob to get any updates to the routing table. bob.converge([&alice]); - test( - "examples/rad-fetch.md", - working.join("bob"), - Some(&bob.home), - [], - ) - .unwrap(); - test( - "examples/rad-fork.md", - working.join("bob"), - Some(&bob.home), - [], - ) - .unwrap(); + environment.tests(["rad-fetch", "rad-fork"], &bob).unwrap(); } #[test] fn rad_diff() { - let working = tempfile::tempdir().unwrap(); + let tmp = tempfile::tempdir().unwrap(); - fixtures::repository(&working); + fixtures::repository(&tmp); - test("examples/rad-diff.md", working, None, []).unwrap(); + test("examples/rad-diff.md", tmp, None, []).unwrap(); } #[test] // User tries to clone; no seeds are available, but user has the repo locally. fn test_clone_without_seeds() { let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); + let mut alice = environment.node("alice"); + let working = environment.tempdir().join("working"); let rid = alice.project("heartwood", "Radicle Heartwood Protocol & Stack"); let mut alice = alice.spawn(); let seeds = alice.handle.seeds(rid).unwrap(); @@ -2065,8 +1565,8 @@ fn test_clone_without_seeds() { fn test_cob_replication() { let mut environment = Environment::new(); let working = tempfile::tempdir().unwrap(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); let rid = alice.project("heartwood", ""); @@ -2134,8 +1634,8 @@ fn test_cob_replication() { fn test_cob_deletion() { let mut environment = Environment::new(); let working = tempfile::tempdir().unwrap(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); let rid = alice.project("heartwood", ""); @@ -2186,10 +1686,10 @@ fn test_cob_deletion() { #[test] fn rad_sync() { let mut environment = Environment::new(); - let working = environment.tmp().join("working"); - let alice = environment.node(config::seed("alice")); - let bob = environment.node(config::seed("bob")); - let eve = environment.node(config::seed("eve")); + let working = environment.tempdir().join("working"); + let alice = environment.seed("alice"); + let bob = environment.seed("bob"); + let eve = environment.seed("eve"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); fixtures::repository(working.join("acme")); @@ -2233,13 +1733,12 @@ fn rad_sync() { // fn test_replication_via_seed() { let mut environment = Environment::new(); - let alice = environment.node(config::relay("alice")); - let bob = environment.node(config::relay("bob")); - let seed = environment.node(Config { + let alice = environment.relay("alice"); + let bob = environment.relay("bob"); + let seed = environment.node_with(Config { seeding_policy: DefaultSeedingPolicy::permissive(), ..config::relay("seed") }); - let working = environment.tmp().join("working"); let rid = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); let mut alice = alice.spawn(); @@ -2257,7 +1756,7 @@ fn test_replication_via_seed() { bob.routes_to(&[]); // Initialize a repo as Alice. - fixtures::repository(working.join("alice")); + environment.repository(&alice); alice .rad( "init", @@ -2270,12 +1769,12 @@ fn test_replication_via_seed() { "master", "--public", ], - working.join("alice"), + environment.work(&alice), ) .unwrap(); alice - .rad("follow", &[&bob.id.to_human()], working.join("alice")) + .rad("follow", &[&bob.id.to_human()], environment.work(&alice)) .unwrap(); alice.routes_to(&[(rid, alice.id), (rid, seed.id)]); @@ -2285,7 +1784,7 @@ fn test_replication_via_seed() { let seed_events = seed.handle.events(); let alice_events = alice.handle.events(); - bob.fork(rid, working.join("bob")).unwrap(); + bob.fork(rid, environment.work(&bob)).unwrap(); alice.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]); seed.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]); @@ -2322,18 +1821,17 @@ fn test_replication_via_seed() { #[test] fn rad_remote() { let mut environment = Environment::new(); - let alice = environment.node(config::relay("alice")); - let bob = environment.node(config::relay("bob")); - let eve = environment.node(config::relay("eve")); - let working = environment.tmp().join("working"); + let alice = environment.relay("alice"); + let bob = environment.relay("bob"); + let eve = environment.relay("eve"); let home = alice.home.clone(); let rid = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); // Setup a test repository. - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&home), [], ) @@ -2361,7 +1859,7 @@ fn rad_remote() { test( "examples/rad-remote.md", - working.join("alice"), + environment.work(&alice), Some(&home), [], ) @@ -2371,101 +1869,61 @@ fn rad_remote() { #[test] fn rad_merge_via_push() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); - test( - "examples/rad-init.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-init", &alice).unwrap(); let alice = alice.spawn(); - test( - "examples/rad-merge-via-push.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-merge-via-push", &alice).unwrap(); } #[test] fn rad_merge_after_update() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); - test( - "examples/rad-init.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-init", &alice).unwrap(); let alice = alice.spawn(); - test( - "examples/rad-merge-after-update.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-merge-after-update", &alice).unwrap(); } #[test] fn rad_merge_no_ff() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); - test( - "examples/rad-init.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); - - test( - "examples/rad-merge-no-ff.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment + .tests(["rad-init", "rad-merge-no-ff"], &alice) + .unwrap(); } #[test] fn rad_patch_pull_update() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); let alice = alice.spawn(); let mut bob = bob.spawn(); bob.connect(&alice).converge([&alice]); - formula(&environment.tmp(), "examples/rad-patch-pull-update.md") + formula(&environment.tempdir(), "examples/rad-patch-pull-update.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( @@ -2483,18 +1941,18 @@ fn rad_patch_open_explore() { let mut environment = Environment::new(); let seed = environment - .node(Config { + .node_with(Config { seeding_policy: DefaultSeedingPolicy::permissive(), ..config::seed("seed") }) .spawn(); - let bob = environment.profile(profile::Config { + let bob = environment.profile_with(profile::Config { preferred_seeds: vec![seed.address()], - ..config::profile("bob") + ..environment.config("bob") }); let mut bob = Node::new(bob).spawn(); - let working = environment.tmp().join("working"); + let working = environment.tempdir().join("working"); fixtures::repository(&working); @@ -2514,64 +1972,38 @@ fn rad_patch_open_explore() { #[test] fn rad_init_private() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); - test( - "examples/rad-init-private.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-init-private", &alice).unwrap(); } #[test] fn rad_init_private_no_seed() { - let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); - - fixtures::repository(working.join("alice")); - - test( - "examples/rad-init-private-no-seed.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + Environment::alice(["rad-init-private-no-seed"]); } #[test] fn rad_init_private_seed() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); let alice = alice.spawn(); let mut bob = bob.spawn(); - test( - "examples/rad-init-private.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-init-private", &alice).unwrap(); bob.connect(&alice).converge([&alice]); - formula(&environment.tmp(), "examples/rad-init-private-seed.md") + formula(&environment.tempdir(), "examples/rad-init-private-seed.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( @@ -2586,30 +2018,23 @@ fn rad_init_private_seed() { #[test] fn rad_init_private_clone() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); let alice = alice.spawn(); let mut bob = bob.spawn(); - test( - "examples/rad-init-private.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-init-private", &alice).unwrap(); bob.connect(&alice).converge([&alice]); - formula(&environment.tmp(), "examples/rad-init-private-clone.md") + formula(&environment.tempdir(), "examples/rad-init-private-clone.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( @@ -2624,18 +2049,17 @@ fn rad_init_private_clone() { #[test] fn rad_init_private_clone_seed() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); let alice = alice.spawn(); let mut bob = bob.spawn(); test( "examples/rad-init-private.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -2644,13 +2068,13 @@ fn rad_init_private_clone_seed() { bob.connect(&alice).converge([&alice]); formula( - &environment.tmp(), + &environment.tempdir(), "examples/rad-init-private-clone-seed.md", ) .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( @@ -2665,35 +2089,22 @@ fn rad_init_private_clone_seed() { #[test] fn rad_publish() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); - test( - "examples/rad-init-private.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); - - test( - "examples/rad-publish.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment + .tests(["rad-init-private", "rad-publish"], &alice) + .unwrap(); } #[test] fn framework_home() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); + let alice = environment.node("alice"); + let bob = environment.node("bob"); - formula(&environment.tmp(), "examples/framework/home.md") + formula(&environment.tempdir(), "examples/framework/home.md") .unwrap() .home( "alice", @@ -2712,16 +2123,15 @@ fn framework_home() { #[test] fn git_push_diverge() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -2731,19 +2141,19 @@ fn git_push_diverge() { let mut bob = bob.spawn(); bob.connect(&alice).converge([&alice]); - bob.fork(acme, working.join("bob")).unwrap(); + bob.fork(acme, environment.work(&bob)).unwrap(); alice.has_remote_of(&acme, &bob.id); - formula(&environment.tmp(), "examples/git/git-push-diverge.md") + formula(&environment.tempdir(), "examples/git/git-push-diverge.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob").join("heartwood"), + environment.work(&bob).join("heartwood"), [("RAD_HOME", bob.home.path().display())], ) .run() @@ -2755,17 +2165,16 @@ fn git_push_converge() { use std::fs; let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let eve = environment.node(Config::test(Alias::new("eve"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); + let eve = environment.node("eve"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -2777,37 +2186,37 @@ fn git_push_converge() { bob.connect(&alice).connect(&eve).converge([&alice]); eve.connect(&alice).converge([&alice]); - bob.fork(acme, working.join("bob")).unwrap(); - eve.fork(acme, working.join("eve")).unwrap(); + bob.fork(acme, environment.work(&bob)).unwrap(); + eve.fork(acme, environment.work(&eve)).unwrap(); alice.has_remote_of(&acme, &bob.id); alice.has_remote_of(&acme, &eve.id); fs::write( - working.join("bob").join("heartwood").join("README"), + environment.work(&bob).join("heartwood").join("README"), "Hello\n", ) .unwrap(); fs::write( - working.join("eve").join("heartwood").join("README"), + environment.work(&eve).join("heartwood").join("README"), "Hello, world!\n", ) .unwrap(); - formula(&environment.tmp(), "examples/git/git-push-converge.md") + formula(&environment.tempdir(), "examples/git/git-push-converge.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob").join("heartwood"), + environment.work(&bob).join("heartwood"), [("RAD_HOME", bob.home.path().display())], ) .home( "eve", - working.join("eve").join("heartwood"), + environment.work(&eve).join("heartwood"), [("RAD_HOME", eve.home.path().display())], ) .run() @@ -2817,16 +2226,15 @@ fn git_push_converge() { #[test] fn git_push_amend() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -2836,19 +2244,19 @@ fn git_push_amend() { let mut bob = bob.spawn(); bob.connect(&alice).converge([&alice]); - bob.fork(acme, working.join("bob")).unwrap(); + bob.fork(acme, environment.work(&bob)).unwrap(); alice.has_remote_of(&acme, &bob.id); - formula(&environment.tmp(), "examples/git/git-push-amend.md") + formula(&environment.tempdir(), "examples/git/git-push-amend.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob").join("heartwood"), + environment.work(&bob).join("heartwood"), [("RAD_HOME", bob.home.path().display())], ) .run() @@ -2858,16 +2266,15 @@ fn git_push_amend() { #[test] fn git_push_rollback() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -2877,19 +2284,19 @@ fn git_push_rollback() { let mut bob = bob.spawn(); bob.connect(&alice).converge([&alice]); - bob.fork(acme, working.join("bob")).unwrap(); + bob.fork(acme, environment.work(&bob)).unwrap(); alice.has_remote_of(&acme, &bob.id); - formula(&environment.tmp(), "examples/git/git-push-rollback.md") + formula(&environment.tempdir(), "examples/git/git-push-rollback.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob").join("heartwood"), + environment.work(&bob).join("heartwood"), [("RAD_HOME", bob.home.path().display())], ) .run() @@ -2899,16 +2306,15 @@ fn git_push_rollback() { #[test] fn rad_push_and_pull_patches() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -2918,50 +2324,52 @@ fn rad_push_and_pull_patches() { let mut bob = bob.spawn(); bob.connect(&alice).converge([&alice]); - bob.fork(acme, working.join("bob")).unwrap(); + bob.fork(acme, environment.work(&bob)).unwrap(); alice.has_remote_of(&acme, &bob.id); - formula(&environment.tmp(), "examples/rad-push-and-pull-patches.md") - .unwrap() - .home( - "alice", - working.join("alice"), - [("RAD_HOME", alice.home.path().display())], - ) - .home( - "bob", - working.join("bob").join("heartwood"), - [("RAD_HOME", bob.home.path().display())], - ) - .run() - .unwrap(); + formula( + &environment.tempdir(), + "examples/rad-push-and-pull-patches.md", + ) + .unwrap() + .home( + "alice", + environment.work(&alice), + [("RAD_HOME", alice.home.path().display())], + ) + .home( + "bob", + environment.work(&bob).join("heartwood"), + [("RAD_HOME", bob.home.path().display())], + ) + .run() + .unwrap(); } #[test] fn rad_patch_fetch_1() { let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); - let (repo, _) = fixtures::repository(working.join("alice")); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); + let (repo, _) = environment.repository(&alice); let rid = alice.project_from("heartwood", "Radicle Heartwood Protocol & Stack", &repo); let alice = alice.spawn(); let mut bob = bob.spawn(); bob.connect(&alice).converge([&alice]); - bob.clone(rid, working.join("bob")).unwrap(); + bob.clone(rid, environment.work(&bob)).unwrap(); - formula(&environment.tmp(), "examples/rad-patch-fetch-1.md") + formula(&environment.tempdir(), "examples/rad-patch-fetch-1.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob").join("heartwood"), + environment.work(&bob).join("heartwood"), [("RAD_HOME", bob.home.path().display())], ) .run() @@ -2971,28 +2379,27 @@ fn rad_patch_fetch_1() { #[test] fn rad_watch() { let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); - let (repo, _) = fixtures::repository(working.join("alice")); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); + let (repo, _) = environment.repository(&alice); let rid = alice.project_from("heartwood", "Radicle Heartwood Protocol & Stack", &repo); let alice = alice.spawn(); let mut bob = bob.spawn(); bob.connect(&alice).converge([&alice]); - bob.clone(rid, working.join("bob")).unwrap(); + bob.clone(rid, environment.work(&bob)).unwrap(); - formula(&environment.tmp(), "examples/rad-watch.md") + formula(&environment.tempdir(), "examples/rad-watch.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob").join("heartwood"), + environment.work(&bob).join("heartwood"), [("RAD_HOME", bob.home.path().display())], ) .run() @@ -3002,11 +2409,10 @@ fn rad_watch() { #[test] fn rad_inbox() { let mut environment = Environment::new(); - let mut alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); - let (repo1, _) = fixtures::repository(working.join("alice").join("heartwood")); - let (repo2, _) = fixtures::repository(working.join("alice").join("radicle-git")); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); + let (repo1, _) = fixtures::repository(environment.work(&alice).join("heartwood")); + let (repo2, _) = fixtures::repository(environment.work(&alice).join("radicle-git")); let rid1 = alice.project_from("heartwood", "Radicle Heartwood Protocol & Stack", &repo1); let rid2 = alice.project_from("radicle-git", "Radicle Git", &repo2); @@ -3014,19 +2420,19 @@ fn rad_inbox() { let mut bob = bob.spawn(); bob.connect(&alice).converge([&alice]); - bob.clone(rid1, working.join("bob")).unwrap(); - bob.clone(rid2, working.join("bob")).unwrap(); + bob.clone(rid1, environment.work(&bob)).unwrap(); + bob.clone(rid2, environment.work(&bob)).unwrap(); - formula(&environment.tmp(), "examples/rad-inbox.md") + formula(&environment.tempdir(), "examples/rad-inbox.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob"), + environment.work(&bob), [("RAD_HOME", bob.home.path().display())], ) .run() @@ -3036,40 +2442,26 @@ fn rad_inbox() { #[test] fn rad_patch_fetch_2() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); - test( - "examples/rad-init.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); - - test( - "examples/rad-patch-fetch-2.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment + .tests(["rad-init", "rad-patch-fetch-2"], &alice) + .unwrap(); } #[test] fn git_push_and_fetch() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); test( "examples/rad-init.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -3080,52 +2472,21 @@ fn git_push_and_fetch() { bob.connect(&alice).converge([&alice]); - test( - "examples/rad-clone.md", - working.join("bob"), - Some(&bob.home), - [], - ) - .unwrap(); - test( - "examples/git/git-push.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); - test( - "examples/git/git-fetch.md", - working.join("bob"), - Some(&bob.home), - [], - ) - .unwrap(); - test( - "examples/git/git-push-delete.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-clone", &bob).unwrap(); + environment.test("git/git-push", &alice).unwrap(); + environment.test("git/git-fetch", &bob).unwrap(); + environment.test("git/git-push-delete", &alice).unwrap(); } #[test] fn git_tag() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); - test( - "examples/rad-init.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("rad-init", &alice).unwrap(); let alice = alice.spawn(); let mut bob = bob.spawn(); @@ -3134,21 +2495,22 @@ fn git_tag() { test( "examples/rad-clone.md", - working.join("bob"), + environment.work(&bob), Some(&bob.home), [], ) .unwrap(); - formula(&environment.tmp(), "examples/git/git-tag.md") + + formula(&environment.tempdir(), "examples/git/git-tag.md") .unwrap() .home( "alice", - working.join("alice"), + environment.work(&alice), [("RAD_HOME", alice.home.path().display())], ) .home( "bob", - working.join("bob"), + environment.work(&bob), [("RAD_HOME", bob.home.path().display())], ) .run() @@ -3158,36 +2520,23 @@ fn git_tag() { #[test] fn rad_workflow() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let working = environment.tmp().join("working"); + let alice = environment.node("alice"); + let bob = environment.node("bob"); - fixtures::repository(working.join("alice")); + environment.repository(&alice); - test( - "examples/workflow/1-new-project.md", - working.join("alice"), - Some(&alice.home), - [], - ) - .unwrap(); + environment.test("workflow/1-new-project", &alice).unwrap(); let alice = alice.spawn(); let mut bob = bob.spawn(); bob.connect(&alice).converge([&alice]); - test( - "examples/workflow/2-cloning.md", - working.join("bob"), - Some(&bob.home), - [], - ) - .unwrap(); + environment.test("workflow/2-cloning", &bob).unwrap(); test( "examples/workflow/3-issues.md", - working.join("bob").join("heartwood"), + environment.work(&bob).join("heartwood"), Some(&bob.home), [], ) @@ -3195,7 +2544,7 @@ fn rad_workflow() { test( "examples/workflow/4-patching-contributor.md", - working.join("bob").join("heartwood"), + environment.work(&bob).join("heartwood"), Some(&bob.home), [], ) @@ -3203,7 +2552,7 @@ fn rad_workflow() { test( "examples/workflow/5-patching-maintainer.md", - working.join("alice"), + environment.work(&alice), Some(&alice.home), [], ) @@ -3211,7 +2560,7 @@ fn rad_workflow() { test( "examples/workflow/6-pulling-contributor.md", - working.join("bob").join("heartwood"), + environment.work(&bob).join("heartwood"), Some(&bob.home), [], ) diff --git a/crates/radicle-cli/tests/util/environment.rs b/crates/radicle-cli/tests/util/environment.rs new file mode 100644 index 00000000..dfa91148 --- /dev/null +++ b/crates/radicle-cli/tests/util/environment.rs @@ -0,0 +1,316 @@ +use std::path::PathBuf; +use std::str::FromStr; + +use localtime::LocalTime; +use radicle::cob::cache::COBS_DB_FILE; +use radicle::crypto::ssh::{keystore::MemorySigner, Keystore}; +use radicle::crypto::{KeyPair, Seed}; +use radicle::node::policy::store as policy; +use radicle::node::{self, UserAgent}; +use radicle::node::{Alias, Config, POLICIES_DB_FILE}; +use radicle::profile::Home; +use radicle::profile::{self}; +use radicle::storage::git::transport; +use radicle::{Profile, Storage}; + +use radicle_node::test::node::{Node, NodeHandle}; + +use crate::util::formula::formula; + +pub(crate) mod config { + use super::*; + use radicle::node::config::{Config, Limits, Network, RateLimit, RateLimits}; + + /// Configuration for a test seed node. + /// + /// It sets the `RateLimit::capacity` to `usize::MAX` ensuring + /// that there are no rate limits for test nodes, since they all + /// operate on the same IP address. This prevents any announcement + /// messages from being dropped. + pub fn seed(alias: &'static str) -> Config { + Config { + network: Network::Test, + relay: node::config::Relay::Always, + limits: Limits { + rate: RateLimits { + inbound: RateLimit { + fill_rate: 1.0, + capacity: usize::MAX, + }, + outbound: RateLimit { + fill_rate: 1.0, + capacity: usize::MAX, + }, + }, + ..Limits::default() + }, + external_addresses: vec![node::Address::from_str(&format!( + "{alias}.radicle.example:8776" + )) + .unwrap()], + ..node(alias) + } + } + + /// Relay node config. + pub fn relay(alias: &'static str) -> Config { + Config { + relay: node::config::Relay::Always, + ..node(alias) + } + } + + /// Test node config. + pub fn node(alias: &'static str) -> Config { + Config::test(Alias::new(alias)) + } +} + +/// Test environment. +pub struct Environment { + tempdir: tempfile::TempDir, + users: usize, +} + +impl Default for Environment { + fn default() -> Self { + Self::new() + } +} + +impl Environment { + /// Create a new test environment. + fn named(name: &'static str) -> Self { + Self { + tempdir: tempfile::TempDir::with_prefix("radicle-".to_owned() + name).unwrap(), + users: 0, + } + } + + /// Create a new test environment. + pub fn new() -> Self { + Self::named("") + } + + /// Return the temp directory path. + pub fn tempdir(&self) -> PathBuf { + self.tempdir.path().into() + } + + /// Path to the working directory designated for given alias. + pub fn work(&self, has_alias: &impl HasAlias) -> PathBuf { + self.tempdir().join("work").join(has_alias.alias().as_ref()) + } + + /// We don't have `RAD_HOME` or `HOME` to rely on to compute a home as usual. + pub fn home(&self, alias: &Alias) -> Home { + Home::new( + self.tempdir() + .join("home") + .join(alias.to_string()) + .join(".radicle"), + ) + .unwrap() + } + + /// Create a new default configuration. + pub fn config(&self, alias: &str) -> profile::Config { + let alias = Alias::new(alias); + profile::Config { + node: node::Config::test(alias), + cli: radicle::cli::Config { hints: false }, + public_explorer: radicle::explorer::Explorer::default(), + preferred_seeds: vec![], + web: radicle::web::Config::default(), + } + } + + /// Create a new profile in this environment. + /// This should be used when a running node is not required. + /// Using this function is only necessary if the desired configuration + /// differs from the default provided by [`Environment::config`] as + /// for this default the convenience function [`Environment::profile`] + /// is provided. + pub fn profile_with(&mut self, config: profile::Config) -> Profile { + let alias = config.alias().clone(); + let home = self.home(&alias); + let keypair = KeyPair::from_seed(Seed::from([!(self.users as u8); 32])); + let policies_db = home.node().join(POLICIES_DB_FILE); + let cobs_db = home.cobs().join(COBS_DB_FILE); + + config.write(&home.config()).unwrap(); + + let storage = Storage::open( + home.storage(), + radicle::git::UserInfo { + alias: alias.clone(), + key: keypair.pk.into(), + }, + ) + .unwrap(); + + let mut db = home.cobs_db_mut().unwrap(); + db.migrate(radicle::cob::migrate::ignore).unwrap(); + + policy::Store::open(policies_db).unwrap(); + home.database_mut() + .unwrap() + .init( + &keypair.pk.into(), + config.node.features(), + &alias, + &UserAgent::default(), + LocalTime::now().into(), + config.node.external_addresses.iter(), + ) + .unwrap(); + + radicle::cob::cache::Store::open(cobs_db).unwrap(); + + transport::local::register(storage.clone()); + let keystore = Keystore::new(&home.keys()); + keystore.store(keypair.clone(), "radicle", None).unwrap(); + + // Ensures that each user has a unique but deterministic public key. + self.users += 1; + + Profile { + home, + storage, + keystore, + public_key: keypair.pk.into(), + config, + } + } + + /// Create a new profile using a the default configuration from [`Environment::config`]. + pub fn profile(&mut self, alias: &'static str) -> Profile { + self.profile_with(self.config(alias)) + } + + /// Create a new node in this environment. This should be used when a running node + /// is required. Use [`Environment::profile`] otherwise. + /// Using this function is only necessary when the node configuration differs + /// from the standard ones ([`config::node`], [`config::relay`], [`config::seed`]), + /// as for each of them a convenience function + /// (resp. [`Environment::node`], [`Environment::relay`], [`Environment::seed`]). + /// is provided to reduce boilerplate. + pub fn node_with(&mut self, node: Config) -> Node { + let alias = node.alias.clone(); + let profile = self.profile_with(profile::Config { + node, + ..self.config(alias.as_ref()) + }); + Node::new(profile) + } + + /// Convenience method for creating a [`Node`] + /// using configuration [`config::node`] within this [`Environment`]. + pub fn node(&mut self, alias: &'static str) -> Node { + self.node_with(config::node(alias)) + } + + /// Convenience method for creating a [`Node`] + /// using configuration [`config::relay`] within this [`Environment`]. + pub fn relay(&mut self, alias: &'static str) -> Node { + self.node_with(config::relay(alias)) + } + + /// Convenience method for creating a [`Node`] + /// using configuration [`config::seed`] within this [`Environment`]. + pub fn seed(&mut self, alias: &'static str) -> Node { + self.node_with(config::seed(alias)) + } + + /// Convenience method for placing repository fixture. + pub fn repository( + &self, + has_alias: &impl HasAlias, + ) -> (radicle_cli::git::Repository, radicle_cli::git::Oid) { + radicle::test::fixtures::repository(self.work(has_alias).as_path()) + } + + // Convenience method for exectuing a test formula with standard configuration. + pub fn test( + &self, + test_file: &'static str, + subject: &(impl HasAlias + HasHome), + ) -> Result<(), Box> { + formula( + self.work(subject).as_ref(), + PathBuf::from("examples").join(test_file.to_owned() + ".md"), + )? + .env( + "RAD_HOME", + subject.home().path().to_path_buf().to_string_lossy(), + ) + .run()?; + + Ok(()) + } + + pub fn tests( + &self, + test_files: impl IntoIterator, + subject: &(impl HasAlias + HasHome), + ) -> Result<(), Box> { + for test_file in test_files { + self.test(test_file, subject)?; + } + + Ok(()) + } + + /// Convenience method for creating exactly one profile with alias "alice" + /// and running tests within it. + pub fn alice(test_files: impl IntoIterator) { + let mut env = Environment::new(); + let alice = env.profile("alice"); + env.repository(&alice); + env.tests(test_files, &alice).unwrap(); + } +} + +pub trait HasAlias { + fn alias(&self) -> &Alias; +} + +impl HasAlias for Node { + fn alias(&self) -> &Alias { + &self.config.alias + } +} + +impl HasAlias for Profile { + fn alias(&self) -> &Alias { + self.config.alias() + } +} + +impl HasAlias for NodeHandle { + fn alias(&self) -> &Alias { + &self.alias + } +} + +pub trait HasHome { + fn home(&self) -> &Home; +} + +impl HasHome for Profile { + fn home(&self) -> &Home { + &self.home + } +} + +impl HasHome for Node { + fn home(&self) -> &Home { + &self.home + } +} + +impl HasHome for NodeHandle { + fn home(&self) -> &Home { + &self.home + } +} diff --git a/crates/radicle-cli/tests/util/formula.rs b/crates/radicle-cli/tests/util/formula.rs new file mode 100644 index 00000000..fe44a0de --- /dev/null +++ b/crates/radicle-cli/tests/util/formula.rs @@ -0,0 +1,39 @@ +use std::path::Path; + +use radicle::profile::env; + +use radicle_cli_test::TestFormula; + +pub(crate) fn formula( + root: &Path, + test: impl AsRef, +) -> Result> { + const RAD_SEED: &str = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + + let mut formula = TestFormula::new(root.to_path_buf()); + let base = Path::new(env!("CARGO_MANIFEST_DIR")); + + formula + .env("GIT_AUTHOR_DATE", "1671125284") + .env("GIT_AUTHOR_EMAIL", "radicle@localhost") + .env("GIT_AUTHOR_NAME", "radicle") + .env("GIT_COMMITTER_DATE", "1671125284") + .env("GIT_COMMITTER_EMAIL", "radicle@localhost") + .env("GIT_COMMITTER_NAME", "radicle") + .env("EDITOR", "true") + .env("TZ", "UTC") + .env("LANG", "C") + .env("USER", "alice") + .env(env::RAD_PASSPHRASE, "radicle") + .env(env::RAD_KEYGEN_SEED, RAD_SEED) + .env(env::RAD_RNG_SEED, "0") + .env(env::RAD_LOCAL_TIME, "1671125284") + .envs(radicle::git::env::GIT_DEFAULT_CONFIG) + .build(&[ + ("radicle-remote-helper", "git-remote-rad"), + ("radicle-cli", "rad"), + ]) + .file(base.join(test))?; + + Ok(formula) +} diff --git a/crates/radicle-cli/tests/util/mod.rs b/crates/radicle-cli/tests/util/mod.rs new file mode 100644 index 00000000..f1c00199 --- /dev/null +++ b/crates/radicle-cli/tests/util/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod environment; +pub(crate) mod formula; diff --git a/crates/radicle-node/src/test.rs b/crates/radicle-node/src/test.rs index d32fcae9..e4225831 100644 --- a/crates/radicle-node/src/test.rs +++ b/crates/radicle-node/src/test.rs @@ -1,7 +1,7 @@ pub mod arbitrary; -pub mod environment; pub mod gossip; pub mod handle; +pub mod node; pub mod peer; pub mod simulator; diff --git a/crates/radicle-node/src/test/node.rs b/crates/radicle-node/src/test/node.rs new file mode 100644 index 00000000..e583db75 --- /dev/null +++ b/crates/radicle-node/src/test/node.rs @@ -0,0 +1,532 @@ +use std::io::BufRead as _; +use std::mem::ManuallyDrop; +use std::path::Path; +use std::{ + collections::{BTreeMap, BTreeSet}, + fs, io, iter, net, process, thread, time, + time::Duration, +}; + +use crossbeam_channel as chan; + +use radicle::cob; +use radicle::cob::issue; +use radicle::crypto::signature::Signer; +use radicle::crypto::ssh::keystore::MemorySigner; +use radicle::crypto::test::signer::MockSigner; +use radicle::crypto::Signature; +use radicle::git; +use radicle::git::refname; +use radicle::identity::{RepoId, Visibility}; +use radicle::node::config::ConnectAddress; +use radicle::node::policy::store as policy; +use radicle::node::seed::Store as _; +use radicle::node::Config; +use radicle::node::{self, Alias}; +use radicle::node::{ConnectOptions, Handle as _}; +use radicle::node::{Database, POLICIES_DB_FILE}; +use radicle::profile::{env, Home, Profile}; +use radicle::rad; +use radicle::storage::{ReadStorage as _, RemoteRepository as _, SignRepository as _}; +use radicle::test::fixtures; +use radicle::Storage; + +use crate::node::device::Device; +use crate::node::NodeId; +use crate::service::Event; +use crate::storage::git::transport; +use crate::{runtime, runtime::Handle, service, Runtime}; + +/// A node that can be run. +pub struct Node { + pub id: NodeId, + pub home: Home, + pub signer: Device, + pub storage: Storage, + pub config: Config, + pub db: service::Stores, + pub policies: policy::Store, +} + +impl Node { + pub fn new(profile: Profile) -> Self { + let signer = Device::from(MemorySigner::load(&profile.keystore, None).unwrap()); + let id = *profile.id(); + let policies_db = profile.home.node().join(POLICIES_DB_FILE); + let policies = policy::Store::open(policies_db).unwrap(); + let db = profile.database_mut().unwrap(); + let db = service::Stores::from(db); + + Node { + id, + home: profile.home, + config: profile.config.node, + signer, + db, + policies, + storage: profile.storage, + } + } +} + +/// Handle to a running node. +pub struct NodeHandle { + pub id: NodeId, + pub alias: Alias, + pub storage: Storage, + pub signer: Device, + pub home: Home, + pub addr: net::SocketAddr, + pub thread: ManuallyDrop>>, + pub handle: ManuallyDrop, +} + +impl Drop for NodeHandle { + fn drop(&mut self) { + log::debug!(target: "test", "Node {} shutting down..", self.id); + + unsafe { ManuallyDrop::take(&mut self.handle) } + .shutdown() + .unwrap(); + unsafe { ManuallyDrop::take(&mut self.thread) } + .join() + .unwrap() + .unwrap(); + } +} + +impl + cyphernet::Ecdh> NodeHandle { + /// Connect this node to another node, and wait for the connection to be established both ways. + pub fn connect(&mut self, remote: &NodeHandle) -> &mut Self { + let local_events = self.handle.events(); + let remote_events = remote.handle.events(); + + self.handle + .connect(remote.id, remote.addr.into(), ConnectOptions::default()) + .ok(); + + local_events + .iter() + .find(|e| { + matches!( + e, Event::PeerConnected { nid } if nid == &remote.id + ) + }) + .unwrap(); + remote_events + .iter() + .find(|e| { + matches!( + e, Event::PeerConnected { nid } if nid == &self.id + ) + }) + .unwrap(); + + self + } + + pub fn disconnect(&mut self, remote: &NodeHandle) { + self.handle.disconnect(remote.id).unwrap(); + } + + /// Shutdown node. + pub fn shutdown(self) { + drop(self) + } + + /// Get the full address of this node. + pub fn address(&self) -> ConnectAddress { + (self.id, node::Address::from(self.addr)).into() + } + + /// Get routing table entries. + pub fn routing(&self) -> impl Iterator { + use node::routing::Store as _; + + self.home.routing_mut().unwrap().entries().unwrap() + } + + pub fn inventory(&self) -> impl Iterator + '_ { + self.routing() + .filter(|(_, n)| *n == self.id) + .map(|(r, _)| r) + } + + /// Get sync status of a repo. + pub fn synced_seeds(&self, rid: &RepoId) -> Vec { + let db = Database::reader(self.home.node().join(node::NODE_DB_FILE)).unwrap(); + let seeds = db.seeds_for(rid).unwrap(); + + seeds.into_iter().collect::, _>>().unwrap() + } + + /// Wait until this node's routing table matches the remotes. + pub fn converge<'a>( + &'a self, + remotes: impl IntoIterator>, + ) -> BTreeSet<(RepoId, NodeId)> { + converge(iter::once(self).chain(remotes)) + } + + /// Wait until this node's routing table contains the given routes. + #[track_caller] + pub fn routes_to(&self, routes: &[(RepoId, NodeId)]) { + log::debug!(target: "test", "Waiting for {} to route to {:?}", self.id, routes); + let events = self.handle.events(); + + loop { + let mut remaining: BTreeSet<_> = routes.iter().collect(); + + for (rid, nid) in self.routing() { + if !remaining.remove(&(rid, nid)) { + log::debug!(target: "test", "Found unexpected route for {}: ({rid}, {nid})", self.id); + } + } + if remaining.is_empty() { + break; + } + events + .wait( + |e| matches!(e, Event::SeedDiscovered { .. }).then_some(()), + time::Duration::from_secs(6), + ) + .unwrap(); + } + } + + /// 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 a repository. + #[track_caller] + pub fn has_repository(&self, rid: &RepoId) { + log::debug!(target: "test", "Waiting for {} to have {rid}", self.id); + let events = self.handle.events(); + + loop { + if self.storage.repository(*rid).is_ok() { + log::debug!(target: "test", "Node {} has {rid}", self.id); + break; + } + events + .wait( + |e| matches!(e, Event::RefsFetched { .. }).then_some(()), + time::Duration::from_secs(6), + ) + .unwrap(); + } + } + + /// Wait until this node has the inventory of another node. + #[track_caller] + pub fn has_remote_of(&self, rid: &RepoId, nid: &NodeId) { + log::debug!(target: "test", "Waiting for {} to have {rid}/{nid}", self.id); + let events = self.handle.events(); + + loop { + if let Ok(repo) = self.storage.repository(*rid) { + if repo.remote(nid).is_ok() { + log::debug!(target: "test", "Node {} has {rid}/{nid}", self.id); + break; + } + } + events + .wait( + |e| matches!(e, Event::RefsFetched { .. }).then_some(()), + time::Duration::from_secs(6), + ) + .unwrap(); + } + } + + /// Clone a repo into a directory. + pub fn clone>(&self, rid: RepoId, cwd: P) -> io::Result<()> { + self.rad("clone", &[rid.to_string().as_str()], cwd) + } + + /// Fork a repo. + pub fn fork>(&self, rid: RepoId, cwd: P) -> io::Result<()> { + self.clone(rid, &cwd)?; + self.rad("fork", &[rid.to_string().as_str()], &cwd)?; + self.announce(rid, 1, &cwd)?; + + Ok(()) + } + + /// Announce a repo. + pub fn announce>(&self, rid: RepoId, replicas: usize, cwd: P) -> io::Result<()> { + self.rad( + "sync", + &[ + rid.to_string().as_str(), + "--announce", + "--replicas", + replicas.to_string().as_str(), + ], + cwd, + ) + } + + /// Init a repo. + pub fn init>(&self, name: &str, desc: &str, cwd: P) -> io::Result<()> { + self.rad( + "init", + &[ + "--name", + name, + "--description", + desc, + "--default-branch", + "master", + "--public", + ], + cwd, + ) + } + + /// Run a `rad` CLI command. + pub fn rad>(&self, cmd: &str, args: &[&str], cwd: P) -> io::Result<()> { + let cwd = cwd.as_ref(); + log::debug!(target: "test", "Running `rad {cmd} {args:?}` in {}..", cwd.display()); + + fs::create_dir_all(cwd)?; + + let result = process::Command::new(snapbox::cmd::cargo_bin("rad")) + .env_clear() + .envs(env::vars().filter(|(k, _)| k == "PATH")) + .env("GIT_AUTHOR_DATE", "1671125284") + .env("GIT_AUTHOR_EMAIL", "radicle@localhost") + .env("GIT_AUTHOR_NAME", "radicle") + .env("GIT_COMMITTER_DATE", "1671125284") + .env("GIT_COMMITTER_EMAIL", "radicle@localhost") + .env("GIT_COMMITTER_NAME", "radicle") + .env( + env::RAD_HOME, + self.home.path().to_string_lossy().to_string(), + ) + .env(env::RAD_PASSPHRASE, "radicle") + .env(env::RAD_LOCAL_TIME, "1671125284") + .env("TZ", "UTC") + .env("LANG", "C") + .envs(git::env::GIT_DEFAULT_CONFIG) + .current_dir(cwd) + .arg(cmd) + .args(args) + .output()?; + + for line in io::BufReader::new(io::Cursor::new(&result.stdout)) + .lines() + .map_while(Result::ok) + { + log::debug!(target: "test", "rad {cmd}: {line}"); + } + + log::debug!( + target: "test", + "Ran command `rad {cmd}` (status={})", result.status.code().unwrap() + ); + + if !result.status.success() { + return Err(io::ErrorKind::Other.into()); + } + Ok(()) + } + + /// Create an [`issue::Issue`] in the `NodeHandle`'s storage. + pub fn issue(&self, rid: RepoId, title: &str, desc: &str) -> cob::ObjectId { + let repo = self.storage.repository(rid).unwrap(); + let mut issues = issue::Cache::no_cache(&repo).unwrap(); + *issues + .create(title, desc, &[], &[], [], &self.signer) + .unwrap() + .id() + } +} + +impl Node { + /// Create a new node. + pub fn init(base: &Path, config: Config) -> Self { + let home = base.join( + iter::repeat_with(fastrand::alphanumeric) + .take(8) + .collect::(), + ); + let home = Home::new(home).unwrap(); + let signer = Device::mock(); + let storage = Storage::open( + home.storage(), + git::UserInfo { + alias: config.alias.clone(), + key: *signer.public_key(), + }, + ) + .unwrap(); + let policies = home.policies_mut().unwrap(); + let db = home.database_mut().unwrap(); + let db = service::Stores::from(db); + + log::debug!(target: "test", "Node::init {}: {}", config.alias, signer.public_key()); + Self { + id: *signer.public_key(), + home, + signer, + storage, + config, + db, + policies, + } + } +} + +impl + Signer + Clone> Node { + /// Spawn a node in its own thread. + pub fn spawn(self) -> NodeHandle { + let alias = self.config.alias.clone(); + let listen = vec![([0, 0, 0, 0], 0).into()]; + let (_, signals) = chan::bounded(1); + let rt = Runtime::init( + self.home.clone(), + self.config, + listen, + signals, + self.signer.clone(), + ) + .unwrap(); + let addr = *rt.local_addrs.first().unwrap(); + let id = *self.signer.public_key(); + let handle = ManuallyDrop::new(rt.handle.clone()); + let thread = ManuallyDrop::new(runtime::thread::spawn(&id, "runtime", move || rt.run())); + + NodeHandle { + id, + alias, + storage: self.storage, + signer: self.signer, + home: self.home, + addr, + handle, + thread, + } + } + + /// 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, + ) -> RepoId { + transport::local::register(self.storage.clone()); + + let branch = refname!("master"); + let id = rad::init( + repo, + name.try_into().unwrap(), + description, + branch.clone(), + Visibility::default(), + &self.signer, + &self.storage, + ) + .map(|(id, _, _)| id) + .unwrap(); + + assert!(self.policies.seed(&id, node::policy::Scope::All).unwrap()); + + log::debug!( + target: "test", + "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(); + + radicle::git::set_upstream( + repo, + &*radicle::rad::REMOTE_NAME, + branch.clone(), + radicle::git::refs::workdir::branch(&branch), + ) + .unwrap(); + + self.storage + .repository(id) + .unwrap() + .sign_refs(&self.signer) + .unwrap(); + + id + } + + /// Populate a storage instance with a project. + pub fn project(&mut self, name: &str, description: &str) -> RepoId { + 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. +#[track_caller] +pub fn converge<'a, G: Signer + cyphernet::Ecdh + 'static>( + nodes: impl IntoIterator>, +) -> BTreeSet<(RepoId, NodeId)> { + let nodes = nodes.into_iter().collect::>(); + + let mut all_routes = BTreeSet::<(RepoId, NodeId)>::new(); + let mut remaining = BTreeMap::from_iter(nodes.iter().map(|node| (node.id, node))); + + // First build the set of all routes. + for node in &nodes { + // Routes from the routing table. + for (rid, seed_id) in node.routing() { + all_routes.insert((rid, seed_id)); + } + // Routes from the local inventory. + for rid in node.inventory() { + all_routes.insert((rid, node.id)); + } + } + + // Then, while there are nodes remaining to converge, check each node to see if + // its routing table has all routes. If so, remove it from the remaining nodes. + while !remaining.is_empty() { + remaining.retain(|_, node| { + let routing = node.routing(); + let routes = BTreeSet::from_iter(routing); + + if routes.is_superset(&all_routes) { + log::debug!(target: "test", "Node {} has converged", node.id); + return false; + } else { + let diff = all_routes.symmetric_difference(&routes).collect::>(); + log::debug!(target: "test", "Node has missing routes: {diff:?}"); + } + true + }); + thread::sleep(Duration::from_millis(100)); + } + all_routes +} diff --git a/crates/radicle-node/src/tests/e2e.rs b/crates/radicle-node/src/tests/e2e.rs index c5be51fe..6b0d17c3 100644 --- a/crates/radicle-node/src/tests/e2e.rs +++ b/crates/radicle-node/src/tests/e2e.rs @@ -15,8 +15,8 @@ use crate::node::{Config, ConnectOptions}; use crate::service; use crate::service::policy::Scope; use crate::storage::git::transport; -use crate::test::environment::{converge, Environment, Node}; use crate::test::logger; +use crate::test::node::{converge, Node}; mod config { use super::*; @@ -29,6 +29,17 @@ mod config { ..Config::test(Alias::new(alias)) } } + + /// Get the scale or "test size". This is used to scale tests with more + /// data. Defaults to `1`. + pub fn scale() -> usize { + std::env::var("RAD_TEST_SCALE") + .map(|s| { + s.parse() + .expect("repository: invalid value for `RAD_TEST_SCALE`") + }) + .unwrap_or(1) + } } #[test] @@ -657,12 +668,11 @@ fn test_fetch_unseeded() { fn test_large_fetch() { logger::init(log::Level::Debug); - let env = Environment::new(); - let scale = env.scale(); - let mut alice = Node::init(&env.tmp(), config::relay("alice")); - let bob = Node::init(&env.tmp(), config::relay("bob")); - let tmp = tempfile::tempdir().unwrap(); + let scale = config::scale(); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); + let (repo, _) = fixtures::repository(tmp.path()); fixtures::populate(&repo, scale.max(3)); @@ -695,8 +705,8 @@ fn test_large_fetch() { fn test_concurrent_fetches() { logger::init(log::Level::Debug); - let env = Environment::new(); - let scale = env.scale(); + let tmp = tempfile::tempdir().unwrap(); + let scale = config::scale(); let repos = scale.max(4); let limits = Limits { // Have one fetch be queued. @@ -706,7 +716,7 @@ fn test_concurrent_fetches() { let mut bob_repos = HashSet::new(); let mut alice_repos = HashSet::new(); let mut alice = Node::init( - &env.tmp(), + tmp.path(), service::Config { limits: limits.clone(), relay: radicle::node::config::Relay::Always, @@ -714,7 +724,7 @@ fn test_concurrent_fetches() { }, ); let mut bob = Node::init( - &env.tmp(), + tmp.path(), service::Config { limits, relay: radicle::node::config::Relay::Always,