cli: Add `rad-clone` test
This commit is contained in:
parent
a8531eff36
commit
2649e9c6fe
|
|
@ -0,0 +1,45 @@
|
||||||
|
To create a local copy of a repository on the radicle network, we use the
|
||||||
|
`clone` command, followed by the identifier or *RID* of the repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad clone rad:zVNuptPuk5XauitpCWSNVCXGGfXW
|
||||||
|
ok Tracking relationship established for rad:zVNuptPuk5XauitpCWSNVCXGGfXW
|
||||||
|
ok Fetching rad:zVNuptPuk5XauitpCWSNVCXGGfXW from z6MknSL…StBU8Vi..
|
||||||
|
ok Forking under z6Mkt67…v4N1tRk..
|
||||||
|
ok Creating checkout in ./acme..
|
||||||
|
ok Remote z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi created
|
||||||
|
ok Remote-tracking branch z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||||
|
|
||||||
|
🌱 Project successfully cloned under [..]/acme/
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
We can now have a look at the new working copy that was created from the cloned
|
||||||
|
repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd acme
|
||||||
|
$ ls
|
||||||
|
README
|
||||||
|
$ cat README
|
||||||
|
Hello World!
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's check that the remote tracking branch was setup correctly:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git branch --remotes
|
||||||
|
rad/master
|
||||||
|
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||||
|
```
|
||||||
|
|
||||||
|
The first branch is ours, and the second points to the repository delegate.
|
||||||
|
We can also take a look at the remotes:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git remote -v
|
||||||
|
rad rad://zVNuptPuk5XauitpCWSNVCXGGfXW/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (fetch)
|
||||||
|
rad rad://zVNuptPuk5XauitpCWSNVCXGGfXW/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (push)
|
||||||
|
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://zVNuptPuk5XauitpCWSNVCXGGfXW/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (fetch)
|
||||||
|
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://zVNuptPuk5XauitpCWSNVCXGGfXW/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
|
||||||
|
```
|
||||||
|
|
@ -144,7 +144,7 @@ pub fn clone<G: Signer>(
|
||||||
// Track.
|
// Track.
|
||||||
if node.track_repo(id)? {
|
if node.track_repo(id)? {
|
||||||
term::success!(
|
term::success!(
|
||||||
"Tracking relationship restablished for {}",
|
"Tracking relationship established for {}",
|
||||||
term::format::tertiary(id)
|
term::format::tertiary(id)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ use radicle::git;
|
||||||
use radicle::profile::Home;
|
use radicle::profile::Home;
|
||||||
use radicle::test::fixtures;
|
use radicle::test::fixtures;
|
||||||
|
|
||||||
use radicle_node::test::environment::Environment;
|
use radicle_node::test::{
|
||||||
|
environment::{Config, Environment},
|
||||||
|
logger,
|
||||||
|
};
|
||||||
|
|
||||||
mod framework;
|
mod framework;
|
||||||
use framework::TestFormula;
|
use framework::TestFormula;
|
||||||
|
|
@ -64,15 +67,15 @@ fn rad_issue() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
let profile = environment.profile("alice");
|
let profile = environment.profile("alice");
|
||||||
let home = &profile.home;
|
let home = &profile.home;
|
||||||
let working = tempfile::tempdir().unwrap();
|
let working = environment.tmp().join("working");
|
||||||
|
|
||||||
// Setup a test repository.
|
// Setup a test repository.
|
||||||
fixtures::repository(working.path());
|
fixtures::repository(&working);
|
||||||
// Set a fixed commit time.
|
// Set a fixed commit time.
|
||||||
env::set_var(radicle_cob::git::RAD_COMMIT_TIME, "1671125284");
|
env::set_var(radicle_cob::git::RAD_COMMIT_TIME, "1671125284");
|
||||||
|
|
||||||
test("examples/rad-init.md", working.path(), Some(home), []).unwrap();
|
test("examples/rad-init.md", &working, Some(home), []).unwrap();
|
||||||
test("examples/rad-issue.md", working.path(), Some(home), []).unwrap();
|
test("examples/rad-issue.md", &working, Some(home), []).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -149,3 +152,23 @@ fn rad_patch() {
|
||||||
test("examples/rad-issue.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();
|
test("examples/rad-patch.md", working.path(), Some(home), []).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rad_clone() {
|
||||||
|
logger::init(log::Level::Debug);
|
||||||
|
|
||||||
|
let mut environment = Environment::new();
|
||||||
|
let mut alice = environment.node("alice");
|
||||||
|
let bob = environment.node("bob");
|
||||||
|
let working = environment.tmp().join("working");
|
||||||
|
|
||||||
|
// Setup a test project.
|
||||||
|
let _ = alice.project("acme", "");
|
||||||
|
|
||||||
|
let alice = alice.spawn(Config::default());
|
||||||
|
let mut bob = bob.spawn(Config::default());
|
||||||
|
|
||||||
|
bob.connect(&alice).converge([&alice]);
|
||||||
|
|
||||||
|
test("examples/rad-clone.md", working, Some(&bob.home), []).unwrap();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,8 @@ impl TestFormula {
|
||||||
pub fn run(&mut self) -> Result<bool, io::Error> {
|
pub fn run(&mut self) -> Result<bool, io::Error> {
|
||||||
let assert = Assert::new().substitutions(self.subs.clone());
|
let assert = Assert::new().substitutions(self.subs.clone());
|
||||||
|
|
||||||
|
fs::create_dir_all(&self.cwd)?;
|
||||||
|
|
||||||
for test in &self.tests {
|
for test in &self.tests {
|
||||||
for assertion in &test.assertions {
|
for assertion in &test.assertions {
|
||||||
let program = if assertion.program == "rad" {
|
let program = if assertion.program == "rad" {
|
||||||
|
|
@ -162,7 +164,14 @@ impl TestFormula {
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from(&assertion.program)
|
PathBuf::from(&assertion.program)
|
||||||
};
|
};
|
||||||
|
log::debug!(target: "test", "Running `{}` in `{}`..", program.display(), self.cwd.display());
|
||||||
|
|
||||||
|
if !program.exists() {
|
||||||
|
log::error!(target: "test", "Program {} does not exist..", program.display());
|
||||||
|
}
|
||||||
|
if !self.cwd.exists() {
|
||||||
|
log::error!(target: "test", "Directory {} does not exist..", self.cwd.display());
|
||||||
|
}
|
||||||
let result = Command::new(program.clone())
|
let result = Command::new(program.clone())
|
||||||
.env_clear()
|
.env_clear()
|
||||||
.envs(env::vars().filter(|(k, _)| k == "PATH"))
|
.envs(env::vars().filter(|(k, _)| k == "PATH"))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, BTreeSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
iter, net, thread,
|
iter, net, thread,
|
||||||
|
|
@ -23,6 +23,8 @@ use crate::node::NodeId;
|
||||||
use crate::storage::git::transport;
|
use crate::storage::git::transport;
|
||||||
use crate::{runtime, runtime::Handle, service, Runtime};
|
use crate::{runtime, runtime::Handle, service, Runtime};
|
||||||
|
|
||||||
|
pub use service::Config;
|
||||||
|
|
||||||
/// Test environment.
|
/// Test environment.
|
||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
tempdir: tempfile::TempDir,
|
tempdir: tempfile::TempDir,
|
||||||
|
|
@ -44,6 +46,11 @@ impl Environment {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the temp directory path.
|
||||||
|
pub fn tmp(&self) -> PathBuf {
|
||||||
|
self.tempdir.path().join("misc")
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new node in this environment. This should be used when a running node
|
/// Create a new node in this environment. This should be used when a running node
|
||||||
/// is required. Use [`Environment::profile`] otherwise.
|
/// is required. Use [`Environment::profile`] otherwise.
|
||||||
pub fn node(&mut self, name: &str) -> Node<MemorySigner> {
|
pub fn node(&mut self, name: &str) -> Node<MemorySigner> {
|
||||||
|
|
@ -60,7 +67,9 @@ impl Environment {
|
||||||
/// Create a new profile in this environment.
|
/// Create a new profile in this environment.
|
||||||
/// This should be used when a running node is not required.
|
/// This should be used when a running node is not required.
|
||||||
pub fn profile(&mut self, name: &str) -> Profile {
|
pub fn profile(&mut self, name: &str) -> Profile {
|
||||||
let home = Home::new(self.tempdir.path().join(name)).init().unwrap();
|
let home = Home::new(self.tmp().join("home").join(name))
|
||||||
|
.init()
|
||||||
|
.unwrap();
|
||||||
let storage = Storage::open(home.storage()).unwrap();
|
let storage = Storage::open(home.storage()).unwrap();
|
||||||
let keystore = Keystore::new(&home.keys());
|
let keystore = Keystore::new(&home.keys());
|
||||||
let keypair = KeyPair::from_seed(Seed::from([!(self.users as u8); 32]));
|
let keypair = KeyPair::from_seed(Seed::from([!(self.users as u8); 32]));
|
||||||
|
|
@ -94,6 +103,7 @@ pub struct NodeHandle<G: Signer + cyphernet::EcSign + 'static> {
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
pub storage: Storage,
|
pub storage: Storage,
|
||||||
pub signer: G,
|
pub signer: G,
|
||||||
|
pub home: Home,
|
||||||
pub addr: net::SocketAddr,
|
pub addr: net::SocketAddr,
|
||||||
pub thread: ManuallyDrop<thread::JoinHandle<Result<(), runtime::Error>>>,
|
pub thread: ManuallyDrop<thread::JoinHandle<Result<(), runtime::Error>>>,
|
||||||
pub handle: ManuallyDrop<Handle<G>>,
|
pub handle: ManuallyDrop<Handle<G>>,
|
||||||
|
|
@ -115,7 +125,7 @@ impl<G: Signer + cyphernet::EcSign + 'static> Drop for NodeHandle<G> {
|
||||||
|
|
||||||
impl<G: Signer + cyphernet::EcSign> NodeHandle<G> {
|
impl<G: Signer + cyphernet::EcSign> NodeHandle<G> {
|
||||||
/// Connect this node to another node, and wait for the connection to be established both ways.
|
/// Connect this node to another node, and wait for the connection to be established both ways.
|
||||||
pub fn connect(&mut self, remote: &NodeHandle<G>) {
|
pub fn connect(&mut self, remote: &NodeHandle<G>) -> &mut Self {
|
||||||
self.handle.connect(remote.id, remote.addr.into()).unwrap();
|
self.handle.connect(remote.id, remote.addr.into()).unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -137,6 +147,15 @@ impl<G: Signer + cyphernet::EcSign> NodeHandle<G> {
|
||||||
}
|
}
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
}
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait until this node's routing table matches the remotes.
|
||||||
|
pub fn converge<'a>(
|
||||||
|
&'a self,
|
||||||
|
remotes: impl IntoIterator<Item = &'a NodeHandle<G>>,
|
||||||
|
) -> BTreeSet<(Id, NodeId)> {
|
||||||
|
converge(iter::once(self).chain(remotes.into_iter()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,7 +186,7 @@ impl<G: cyphernet::EcSign<Pk = NodeId, Sig = Signature> + Signer + Clone> Node<G
|
||||||
let proxy = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
|
let proxy = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
|
||||||
let daemon = ([0, 0, 0, 0], fastrand::u16(1025..)).into();
|
let daemon = ([0, 0, 0, 0], fastrand::u16(1025..)).into();
|
||||||
let rt = Runtime::init(
|
let rt = Runtime::init(
|
||||||
self.home,
|
self.home.clone(),
|
||||||
config,
|
config,
|
||||||
listen,
|
listen,
|
||||||
proxy,
|
proxy,
|
||||||
|
|
@ -189,6 +208,7 @@ impl<G: cyphernet::EcSign<Pk = NodeId, Sig = Signature> + Signer + Clone> Node<G
|
||||||
id,
|
id,
|
||||||
storage: self.storage,
|
storage: self.storage,
|
||||||
signer: self.signer,
|
signer: self.signer,
|
||||||
|
home: self.home,
|
||||||
addr,
|
addr,
|
||||||
handle,
|
handle,
|
||||||
thread,
|
thread,
|
||||||
|
|
@ -196,18 +216,15 @@ impl<G: cyphernet::EcSign<Pk = NodeId, Sig = Signature> + Signer + Clone> Node<G
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Populate a storage instance with a project.
|
/// Populate a storage instance with a project.
|
||||||
pub fn project(&mut self, name: &str) -> Id {
|
pub fn project(&mut self, name: &str, description: &str) -> Id {
|
||||||
transport::local::register(self.storage.clone());
|
transport::local::register(self.storage.clone());
|
||||||
|
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let (repo, _) = fixtures::gen::repository(tmp.path());
|
let (repo, _) = fixtures::gen::repository(tmp.path());
|
||||||
let description = iter::repeat_with(fastrand::alphabetic)
|
|
||||||
.take(12)
|
|
||||||
.collect::<String>();
|
|
||||||
let id = rad::init(
|
let id = rad::init(
|
||||||
&repo,
|
&repo,
|
||||||
name,
|
name,
|
||||||
&description,
|
description,
|
||||||
refname!("master"),
|
refname!("master"),
|
||||||
&self.signer,
|
&self.signer,
|
||||||
&self.storage,
|
&self.storage,
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ fn test_inventory_sync_basic() {
|
||||||
let mut alice = Node::init(tmp.path());
|
let mut alice = Node::init(tmp.path());
|
||||||
let mut bob = Node::init(tmp.path());
|
let mut bob = Node::init(tmp.path());
|
||||||
|
|
||||||
alice.project("alice");
|
alice.project("alice", "");
|
||||||
bob.project("bob");
|
bob.project("bob", "");
|
||||||
|
|
||||||
let mut alice = alice.spawn(service::Config::default());
|
let mut alice = alice.spawn(service::Config::default());
|
||||||
let bob = bob.spawn(service::Config::default());
|
let bob = bob.spawn(service::Config::default());
|
||||||
|
|
@ -45,9 +45,9 @@ fn test_inventory_sync_bridge() {
|
||||||
let mut bob = Node::init(tmp.path());
|
let mut bob = Node::init(tmp.path());
|
||||||
let mut eve = Node::init(tmp.path());
|
let mut eve = Node::init(tmp.path());
|
||||||
|
|
||||||
alice.project("alice");
|
alice.project("alice", "");
|
||||||
bob.project("bob");
|
bob.project("bob", "");
|
||||||
eve.project("eve");
|
eve.project("eve", "");
|
||||||
|
|
||||||
let mut alice = alice.spawn(service::Config::default());
|
let mut alice = alice.spawn(service::Config::default());
|
||||||
let mut bob = bob.spawn(service::Config::default());
|
let mut bob = bob.spawn(service::Config::default());
|
||||||
|
|
@ -76,10 +76,10 @@ fn test_inventory_sync_ring() {
|
||||||
let mut eve = Node::init(tmp.path());
|
let mut eve = Node::init(tmp.path());
|
||||||
let mut carol = Node::init(tmp.path());
|
let mut carol = Node::init(tmp.path());
|
||||||
|
|
||||||
alice.project("alice");
|
alice.project("alice", "");
|
||||||
bob.project("bob");
|
bob.project("bob", "");
|
||||||
eve.project("eve");
|
eve.project("eve", "");
|
||||||
carol.project("carol");
|
carol.project("carol", "");
|
||||||
|
|
||||||
let mut alice = alice.spawn(service::Config::default());
|
let mut alice = alice.spawn(service::Config::default());
|
||||||
let mut bob = bob.spawn(service::Config::default());
|
let mut bob = bob.spawn(service::Config::default());
|
||||||
|
|
@ -114,11 +114,11 @@ fn test_inventory_sync_star() {
|
||||||
let mut carol = Node::init(tmp.path());
|
let mut carol = Node::init(tmp.path());
|
||||||
let mut dave = Node::init(tmp.path());
|
let mut dave = Node::init(tmp.path());
|
||||||
|
|
||||||
alice.project("alice");
|
alice.project("alice", "");
|
||||||
bob.project("bob");
|
bob.project("bob", "");
|
||||||
eve.project("eve");
|
eve.project("eve", "");
|
||||||
carol.project("carol");
|
carol.project("carol", "");
|
||||||
dave.project("dave");
|
dave.project("dave", "");
|
||||||
|
|
||||||
let alice = alice.spawn(service::Config::default());
|
let alice = alice.spawn(service::Config::default());
|
||||||
let mut bob = bob.spawn(service::Config::default());
|
let mut bob = bob.spawn(service::Config::default());
|
||||||
|
|
@ -142,7 +142,7 @@ fn test_replication() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let alice = Node::init(tmp.path());
|
let alice = Node::init(tmp.path());
|
||||||
let mut bob = Node::init(tmp.path());
|
let mut bob = Node::init(tmp.path());
|
||||||
let acme = bob.project("acme");
|
let acme = bob.project("acme", "");
|
||||||
|
|
||||||
let mut alice = alice.spawn(service::Config::default());
|
let mut alice = alice.spawn(service::Config::default());
|
||||||
let bob = bob.spawn(service::Config::default());
|
let bob = bob.spawn(service::Config::default());
|
||||||
|
|
@ -199,7 +199,7 @@ fn test_clone() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let alice = Node::init(tmp.path());
|
let alice = Node::init(tmp.path());
|
||||||
let mut bob = Node::init(tmp.path());
|
let mut bob = Node::init(tmp.path());
|
||||||
let acme = bob.project("acme");
|
let acme = bob.project("acme", "");
|
||||||
|
|
||||||
let mut alice = alice.spawn(service::Config::default());
|
let mut alice = alice.spawn(service::Config::default());
|
||||||
let bob = bob.spawn(service::Config::default());
|
let bob = bob.spawn(service::Config::default());
|
||||||
|
|
@ -249,7 +249,7 @@ fn test_fetch_up_to_date() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let alice = Node::init(tmp.path());
|
let alice = Node::init(tmp.path());
|
||||||
let mut bob = Node::init(tmp.path());
|
let mut bob = Node::init(tmp.path());
|
||||||
let acme = bob.project("acme");
|
let acme = bob.project("acme", "");
|
||||||
|
|
||||||
let mut alice = alice.spawn(service::Config::default());
|
let mut alice = alice.spawn(service::Config::default());
|
||||||
let bob = bob.spawn(service::Config::default());
|
let bob = bob.spawn(service::Config::default());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue