Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: CLI tests for iroh add, and API changes #343

Merged
merged 4 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 16 additions & 19 deletions iroh-api/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,10 @@ pub trait Api {
&self,
ipfs_path: &IpfsPath,
) -> LocalBoxStream<'_, Result<(RelativePathBuf, OutType)>>;
fn add<'a>(
&'a self,
path: &'a Path,
recursive: bool,
no_wrap: bool,
) -> LocalBoxFuture<'_, Result<Cid>>;

fn add_file<'a>(&'a self, path: &'a Path, wrap: bool) -> LocalBoxFuture<'_, Result<Cid>>;
fn add_dir<'a>(&'a self, path: &'a Path, wrap: bool) -> LocalBoxFuture<'_, Result<Cid>>;

fn check(&self) -> BoxFuture<'_, StatusTable>;
fn watch(&self) -> LocalBoxFuture<'static, LocalBoxStream<'static, StatusTable>>;
}
Expand Down Expand Up @@ -125,23 +123,22 @@ impl Api for Iroh {
.boxed_local()
}

fn add<'a>(
&'a self,
path: &'a Path,
recursive: bool,
no_wrap: bool,
) -> LocalBoxFuture<'_, Result<Cid>> {
fn add_file<'a>(&'a self, path: &'a Path, wrap: bool) -> LocalBoxFuture<'_, Result<Cid>> {
async move {
let providing_client = iroh_resolver::unixfs_builder::StoreAndProvideClient {
client: Box::new(&self.client),
};
if path.is_dir() {
unixfs_builder::add_dir(Some(&providing_client), path, !no_wrap, recursive).await
} else if path.is_file() {
unixfs_builder::add_file(Some(&providing_client), path, !no_wrap).await
} else {
anyhow::bail!("can only add files or directories");
}
unixfs_builder::add_file(Some(&providing_client), path, wrap).await
}
.boxed_local()
}

fn add_dir<'a>(&'a self, path: &'a Path, wrap: bool) -> LocalBoxFuture<'_, Result<Cid>> {
async move {
let providing_client = iroh_resolver::unixfs_builder::StoreAndProvideClient {
client: Box::new(&self.client),
};
unixfs_builder::add_dir(Some(&providing_client), path, wrap).await
}
.boxed_local()
}
Expand Down
12 changes: 11 additions & 1 deletion iroh-api/src/api_ext.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::path::{Path, PathBuf};

use crate::{Api, IpfsPath, OutType};
use crate::{Api, Cid, IpfsPath, OutType};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use futures::Stream;
Expand Down Expand Up @@ -29,6 +29,16 @@ pub trait ApiExt: Api {
save_get_stream(&root_path, blocks).await?;
Ok(root_path)
}

async fn add(&self, path: &Path, wrap: bool) -> Result<Cid> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the code to figure out if it is a file or a dir is moved to ApiExt, and the main fns in api require you to be explicit? I like it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and the check for the recursive flag before copying a directory was removed from the API entirely, because the use case for writing code like this is very different from the reason it's in the CLI (which is to prevent accidents in interactive mode).

if path.is_dir() {
self.add_dir(path, wrap).await
} else if path.is_file() {
self.add_file(path, wrap).await
} else {
anyhow::bail!("can only add files or directories")
}
}
}

impl<T> ApiExt for T where T: Api {}
Expand Down
36 changes: 5 additions & 31 deletions iroh-resolver/src/unixfs_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ pub struct DirectoryBuilder {
name: Option<String>,
entries: Vec<Entry>,
typ: DirectoryType,
recursive: bool,
}

impl Default for DirectoryBuilder {
Expand All @@ -329,7 +328,6 @@ impl Default for DirectoryBuilder {
name: None,
entries: Default::default(),
typ: DirectoryType::Basic,
recursive: false,
}
}
}
Expand All @@ -344,18 +342,12 @@ impl DirectoryBuilder {
self
}

pub fn recursive(&mut self) -> &mut Self {
self.recursive = true;
self
}

pub fn name<N: Into<String>>(&mut self, name: N) -> &mut Self {
self.name = Some(name.into());
self
}

pub fn add_dir(&mut self, dir: Directory) -> Result<&mut Self> {
ensure!(self.recursive, "recursive directories not allowed");
Ok(self.entry(Entry::Directory(dir)))
}

Expand Down Expand Up @@ -471,15 +463,10 @@ pub async fn add_file<S: Store>(store: Option<S>, path: &Path, wrap: bool) -> Re
/// - storing the content using `rpc.store`
/// - returns the root Cid
/// - optionally wraps into a UnixFs directory to preserve the directory name
pub async fn add_dir<S: Store>(
store: Option<S>,
path: &Path,
wrap: bool,
recursive: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the recursive flag not needed anymore? Do we always use recursive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use case for the recursive flag in my mind is to prevent people from accidentally adding a directory in the CLI. That use case doesn't exist on the API level, so I've removed it from the API. The implementation is now on the CLI level which is much simpler than maintaining this in the UnixFS builder.

) -> Result<Cid> {
pub async fn add_dir<S: Store>(store: Option<S>, path: &Path, wrap: bool) -> Result<Cid> {
ensure!(path.is_dir(), "provided path was not a directory");

let dir = make_dir_from_path(path, recursive).await?;
let dir = make_dir_from_path(path).await?;
// encode and store
let mut root = None;
let parts = {
Expand All @@ -504,25 +491,22 @@ pub async fn add_dir<S: Store>(
}

#[async_recursion(?Send)]
async fn make_dir_from_path<P: Into<PathBuf>>(path: P, recursive: bool) -> Result<Directory> {
async fn make_dir_from_path<P: Into<PathBuf>>(path: P) -> Result<Directory> {
let path = path.into();
let mut dir = DirectoryBuilder::new();
dir.name(
path.file_name()
.and_then(|s| s.to_str())
.unwrap_or_default(),
);
if recursive {
dir.recursive();
}
let mut directory_reader = tokio::fs::read_dir(path.clone()).await?;
while let Some(entry) = directory_reader.next_entry().await? {
let path = entry.path();
if path.is_file() {
let f = FileBuilder::new().path(path).build().await?;
dir.add_file(f);
} else if path.is_dir() {
let d = make_dir_from_path(path, recursive).await?;
let d = make_dir_from_path(path).await?;
dir.add_dir(d)?;
} else {
anyhow::bail!("directory entry is neither file nor directory")
Expand Down Expand Up @@ -597,16 +581,7 @@ mod tests {
let dir = DirectoryBuilder::new();
let dir = dir.build()?;

let mut no_recursive = DirectoryBuilder::new();
if no_recursive.add_dir(dir).is_ok() {
panic!("shouldn't be able to add a directory to a non-recursive directory builder");
}

let dir = DirectoryBuilder::new();
let dir = dir.build()?;

let mut recursive_dir_builder = DirectoryBuilder::new();
recursive_dir_builder.recursive();
recursive_dir_builder
.add_dir(dir)
.expect("recursive directories allowed");
Expand Down Expand Up @@ -703,7 +678,6 @@ mod tests {
#[async_recursion(?Send)]
async fn build_directory(name: &str, dir: &TestDir) -> Result<Directory> {
let mut builder = DirectoryBuilder::new();
builder.recursive();
builder.name(name);
for (name, entry) in dir {
match entry {
Expand Down Expand Up @@ -1027,7 +1001,7 @@ mod tests {
entries: vec![Entry::File(file), Entry::Directory(nested_dir)],
};

let mut got = make_dir_from_path(dir, true).await?;
let mut got = make_dir_from_path(dir).await?;

// Before comparison sort entries to make test deterministic.
// The readdir_r function is used in the underlying platform which
Expand Down
29 changes: 28 additions & 1 deletion iroh/src/fixture.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::collections::HashMap;
use std::env;
use std::future;
use std::str::FromStr;

use futures::StreamExt;
use iroh_api::{Lookup, MockApi, MockP2p, OutType, PeerId};
use iroh_api::{Cid, Lookup, MockApi, MockP2p, OutType, PeerId};
use relative_path::RelativePathBuf;

type GetFixture = fn() -> MockApi;
Expand Down Expand Up @@ -51,6 +53,26 @@ fn fixture_get() -> MockApi {
api
}

fn fixture_add_file() -> MockApi {
let mut api = MockApi::default();
api.expect_add_file().returning(|_ipfs_path, _| {
Box::pin(future::ready(
Cid::from_str("QmYbcW4tXLXHWw753boCK8Y7uxLu5abXjyYizhLznq9PUR").map_err(|e| e.into()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this returns the same cid whatever you add. Is the intention to make it more realistic, or just write another mock for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just write another mock if that becomes a use case. In general I don't think we should strive for realism in these mocks, just fulfilling the contract in the easiest way possible for a particular test case.

Unfortunately this means for add there isn't all that much to mock yet. Perhaps that will change somewhat once the add API starts to return more stuff related to progress, though I suspect tests for that will have to be normal Rust unit tests, not trycmd tests.

))
});
api
}

fn fixture_add_directory() -> MockApi {
let mut api = MockApi::default();
api.expect_add_dir().returning(|_ipfs_path, _| {
Box::pin(future::ready(
Cid::from_str("QmYbcW4tXLXHWw753boCK8Y7uxLu5abXjyYizhLznq9PUR").map_err(|e| e.into()),
))
});
api
}

fn fixture_get_wrapped_file() -> MockApi {
let mut api = MockApi::default();
api.expect_get_stream().returning(|_ipfs_path| {
Expand Down Expand Up @@ -90,6 +112,11 @@ fn register_fixtures() -> FixtureRegistry {
"get_unwrapped_file".to_string(),
fixture_get_unwrapped_file as GetFixture,
),
("add_file".to_string(), fixture_add_file as GetFixture),
(
"add_directory".to_string(),
fixture_add_directory as GetFixture,
),
]
.into_iter()
.collect()
Expand Down
8 changes: 7 additions & 1 deletion iroh/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,13 @@ impl Cli {
recursive,
no_wrap,
} => {
let cid = api.add(path, *recursive, *no_wrap).await?;
if path.is_dir() && !*recursive {
anyhow::bail!(
"{} is a directory, use --recursive to add it",
path.display()
);
}
let cid = api.add(path, !(*no_wrap)).await?;
println!("/ipfs/{}", cid);
}
Commands::Get {
Expand Down
Loading