Skip to content

Commit

Permalink
refactor: extract CLI code from main into run to compile fake for tes…
Browse files Browse the repository at this point in the history
…ting purposes

The code in `main` is moved into `run`, which now exposes two versions of
run_cli, one enabled in production and the other one only for testing purposes,
using a fake implementation of the `api` trait
  • Loading branch information
faassen committed Sep 26, 2022
1 parent c30c1e1 commit 899653c
Show file tree
Hide file tree
Showing 5 changed files with 357 additions and 93 deletions.
27 changes: 27 additions & 0 deletions iroh-ctl/run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
async fn main_cli(cli: Cli) -> Result<()> {
let cfg_path = iroh_config_path(CONFIG_FILE_NAME)?;
let sources = vec![Some(cfg_path), cli.cfg.clone()];
let config = make_config(
// default
Config::default(),
// potential config files
sources,
// env var prefix for this config
ENV_PREFIX,
// map of present command line arguments
cli.make_overrides_map(),
)
.unwrap();

let client = Client::new(config.rpc_client).await?;

let api = Api::new(&client).await?;

run_cli_command(&api, cli).await
}

#[cfg(test)]
async fn fake_cli(cli: Cli) -> Result<()> {
let fake = crate::fake::FakeApi::default();
run_cli_command(&api, cli).await
}
214 changes: 214 additions & 0 deletions iroh-ctl/src/fake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
use std::collections::{HashMap, HashSet};
use std::path::Path;

use anyhow::Result;
use async_trait::async_trait;
use bytes::Bytes;
use cid::Cid;
use iroh::api;
use libp2p::gossipsub::MessageId;
use libp2p::{Multiaddr, PeerId};

pub struct FakeApi {
failing: bool,
}

pub struct FakeP2p {
failing: bool,
}

pub struct FakeStore {
failing: bool,
}

impl FakeApi {
pub fn new(failing: bool) -> Self {
Self { failing }
}
}

impl Default for FakeApi {
fn default() -> Self {
Self::new(false)
}
}

impl FakeP2p {
fn new(failing: bool) -> Self {
Self { failing }
}
}

impl Default for FakeP2p {
fn default() -> Self {
Self::new(false)
}
}

impl FakeStore {
fn new(failing: bool) -> Self {
Self { failing }
}
}

impl Default for FakeStore {
fn default() -> Self {
Self::new(false)
}
}

#[async_trait]
impl api::Accessors<FakeP2p, FakeStore> for FakeApi {
fn p2p(&self) -> Result<FakeP2p> {
Ok(FakeP2p::new(self.failing))
}

fn store(&self) -> Result<FakeStore> {
Ok(FakeStore::new(self.failing))
}
}

#[async_trait]
impl api::Main for FakeApi {
async fn version(&self) -> Result<String> {
Ok("0.0.0".to_string())
}
}

#[async_trait]
impl api::GetAdd for FakeApi {
async fn get(&self, cid: &Cid, output: &Path) -> Result<()> {
if self.failing {
return Err(anyhow::anyhow!("failing"));
}
// XXX should really affect the file system
Ok(())
}

async fn add(&self, path: &Path) -> Result<Cid> {
if self.failing {
return Err(anyhow::anyhow!("failing"));
}
Ok(Cid::default())
}
}

#[async_trait]
impl api::P2pConnectDisconnect for FakeP2p {
async fn connect(&self, peer_id: &PeerId, addrs: &[Multiaddr]) -> Result<()> {
Ok(())
}

async fn disconnect(&self, peer_id: &PeerId) -> Result<()> {
Ok(())
}
}

#[async_trait]
impl api::P2pId for FakeP2p {
async fn p2p_version(&self) -> Result<String> {
Ok("0.0.0".to_string())
}

async fn local_peer_id(&self) -> Result<PeerId> {
Ok(PeerId::from_bytes(&[
0, 32, 213, 223, 174, 101, 171, 227, 94, 23, 72, 55, 121, 197, 126, 154, 49, 64, 153,
109, 184, 172, 249, 168, 157, 71, 59, 151, 11, 77, 147, 45, 125, 158,
])?)
}

async fn addrs_listen(&self) -> Result<Vec<Multiaddr>> {
Ok(vec![])
}

async fn addrs_local(&self) -> Result<Vec<Multiaddr>> {
Ok(vec![])
}

async fn id(&self) -> Result<api::Id> {
Ok(api::Id {
peer_id: self.local_peer_id().await?,
listen_addrs: self.addrs_listen().await?,
local_addrs: self.addrs_local().await?,
})
}

async fn peers(&self) -> Result<HashMap<PeerId, Vec<Multiaddr>>> {
let mut m: HashMap<PeerId, Vec<Multiaddr>> = HashMap::new();
let addr1: Multiaddr = "/ip4/127.0.0.1".parse().unwrap();
let addr2: Multiaddr = "/ip4/192.168.1.1".parse().unwrap();
let addr3: Multiaddr = "/ip4/192.168.1.2".parse().unwrap();
let addr4: Multiaddr = "/ip4/192.168.1.4".parse().unwrap();
m.insert(
PeerId::from_bytes(&[
0, 32, 15, 231, 162, 148, 52, 155, 40, 187, 217, 170, 125, 185, 68, 142, 156, 196,
145, 178, 64, 74, 19, 27, 9, 171, 111, 35, 88, 236, 103, 150, 96, 66,
])?,
vec![addr1, addr2],
);
m.insert(
PeerId::from_bytes(&[
0, 32, 144, 137, 53, 144, 57, 13, 191, 157, 254, 110, 136, 212, 131, 241, 179, 29,
38, 29, 207, 62, 126, 215, 213, 49, 248, 43, 143, 40, 123, 93, 248, 222,
])?,
vec![addr3, addr4],
);
Ok(m)
}

async fn ping(&self, ping_args: &[api::Ping], count: usize) -> Result<()> {
Ok(())
}
}

#[async_trait]
impl api::P2pFetch for FakeP2p {
async fn fetch_bitswap(&self, cid: &Cid, providers: &[PeerId]) -> Result<Bytes> {
Ok(Bytes::default())
}

async fn fetch_providers(&self, cid: &Cid) -> Result<HashSet<PeerId>> {
Ok(HashSet::new())
}
}

#[async_trait]
impl api::P2pGossipsub for FakeP2p {
async fn publish(&self, topic: &str, file: Option<&Path>) -> Result<MessageId> {
Ok(MessageId::new(&[]))
}

async fn subscribe(&self, topic: &str) -> Result<bool> {
Ok(true)
}

async fn unsubscribe(&self, topic: &str) -> Result<bool> {
Ok(true)
}
}

#[async_trait]
impl api::StoreMain for FakeStore {
async fn store_version(&self) -> Result<String> {
Ok("0.0.0".to_string())
}

async fn get_links(&self, cid: &Cid) -> Result<Option<Vec<Cid>>> {
Ok(Some(vec![]))
}
}

#[async_trait]
impl api::StoreBlock for FakeStore {
async fn block_get(&self, cid: &Cid) -> Result<Option<Bytes>> {
Ok(Some(Bytes::default()))
}

async fn block_put(&self, data: &Bytes) -> Result<Cid> {
Ok(Cid::default())
}

async fn block_has(&self, cid: &Cid) -> Result<bool> {
Ok(false)
}
}
3 changes: 3 additions & 0 deletions iroh-ctl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
pub mod config;
#[cfg(test)]
mod fake;
pub mod gateway;
pub mod metrics;
pub mod p2p;
pub mod run;
pub mod status;
pub mod store;
99 changes: 6 additions & 93 deletions iroh-ctl/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,97 +1,10 @@
use std::collections::HashMap;
use std::path::PathBuf;

use anyhow::Result;
use clap::{Parser, Subcommand};
use iroh::{api, Api};
use iroh_ctl::{
gateway::{run_command as run_gateway_command, Gateway},
p2p::{run_command as run_p2p_command, P2p},
store::{run_command as run_store_command, Store},
};
use iroh_rpc_client::Client;
use iroh_util::{iroh_config_path, make_config};

use iroh_ctl::{
config::{Config, CONFIG_FILE_NAME, ENV_PREFIX},
status,
};

#[derive(Parser, Debug, Clone)]
#[clap(version, about, long_about = None, propagate_version = true)]
struct Cli {
#[clap(long)]
cfg: Option<PathBuf>,
#[clap(long = "no-metrics")]
no_metrics: bool,
#[clap(subcommand)]
command: Commands,
}

impl Cli {
fn make_overrides_map(&self) -> HashMap<String, String> {
let mut map = HashMap::new();
map.insert("metrics.debug".to_string(), self.no_metrics.to_string());
map
}
}

#[derive(Subcommand, Debug, Clone)]
enum Commands {
/// status checks the health of the different processes
// #[clap(about = "Check the health of the different iroh processes.")]
// Status {
// #[clap(short, long)]
// /// when true, updates the status table whenever a change in a process's status occurs
// watch: bool,
// },
Version,
P2p(P2p),
Store(Store),
Gateway(Gateway),
}
use clap::Parser;

#[tokio::main(flavor = "multi_thread")]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();

let cfg_path = iroh_config_path(CONFIG_FILE_NAME)?;
let sources = vec![Some(cfg_path), cli.cfg.clone()];
let config = make_config(
// default
Config::default(),
// potential config files
sources,
// env var prefix for this config
ENV_PREFIX,
// map of present command line arguments
cli.make_overrides_map(),
)
.unwrap();

let client = Client::new(config.rpc_client).await?;

let api = Api::new(&client).await?;

run_cli_command(&api, cli).await
}

async fn run_cli_command<A: api::Api<P, S>, P: api::P2p, S: api::Store>(
api: &A,
cli: Cli,
) -> Result<()> {
match cli.command {
// Commands::Status { watch } => {
// crate::status::status(client, watch).await?;
// }
Commands::Version => {
let version = api.version().await?;
println!("v{}", version);
}
Commands::P2p(p2p) => run_p2p_command(api.p2p()?, p2p).await?,
Commands::Store(store) => run_store_command(api.store()?, store).await?,
Commands::Gateway(gateway) => run_gateway_command(gateway).await?,
};

Ok(())
async fn main() -> Result<()> {
let cli = iroh_ctl::run::Cli::parse();
// run_cli exists in two versions, one for real client interaction,
// and one for testing purposes using a fake API implementation
iroh_ctl::run::run_cli(cli).await
}
Loading

0 comments on commit 899653c

Please sign in to comment.