Skip to content

Commit

Permalink
Skeleton TryCp test
Browse files Browse the repository at this point in the history
  • Loading branch information
ThetaSinner committed Jun 4, 2024
1 parent a778d59 commit be734ab
Show file tree
Hide file tree
Showing 20 changed files with 432 additions and 15 deletions.
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"bindings/client",
"bindings/runner",
"bindings/trycp_client",
"bindings/trycp_runner",

"happ_builder",

Expand All @@ -20,6 +21,7 @@ members = [
"scenarios/write_read",
"scenarios/write_query",
"scenarios/local_signals",
"scenarios/remote_call",

"zomes/return_single_value/coordinator",
"zomes/crud/coordinator",
Expand All @@ -44,6 +46,7 @@ default-members = [
"bindings/client",
"bindings/runner",
"bindings/trycp_client",
"bindings/trycp_runner",

"happ_builder",
]
Expand Down Expand Up @@ -98,6 +101,7 @@ wind_tunnel_runner = { path = "./framework/runner", version = "0.2.0-alpha.2" }
holochain_client_instrumented = { path = "./bindings/client", version = "0.2.0-alpha.2" }
holochain_wind_tunnel_runner = { path = "./bindings/runner", version = "0.2.0-alpha.2" }
trycp_client_instrumented = { path = "./bindings/trycp_client", version = "0.2.0-alpha.2" }
trycp_wind_tunnel_runner = { path = "./bindings/trycp_runner", version = "0.2.0-alpha.2" }

# hApp Builder
happ_builder = { path = "./happ_builder", version = "0.1.0" }
Expand Down
1 change: 1 addition & 0 deletions bindings/trycp_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ rmp-serde = { workspace = true }
serde = { workspace = true }
rand = { workspace = true }
ed25519-dalek = { workspace = true }
tokio = { workspace = true }

wind_tunnel_instruments = { workspace = true }
wind_tunnel_instruments_derive = { workspace = true }
Expand Down
30 changes: 20 additions & 10 deletions bindings/trycp_client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
use std::io;
use std::sync::Arc;
use std::time::Duration;

use holochain_client::AgentSigner;
use holochain_conductor_api::ZomeCall;
use holochain_types::prelude::{ExternIO, FunctionName, ZomeName};
use holochain_zome_types::cell::CellId;
use holochain_zome_types::prelude::ZomeCallUnsigned;
use serde::de::DeserializeOwned;
use std::io;
use std::sync::Arc;
use std::time::Duration;
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
use trycp_api::MessageResponse;
use trycp_client::{Request, SignalRecv, TrycpClient};

use wind_tunnel_instruments::{OperationRecord, Reporter};
use wind_tunnel_instruments_derive::wind_tunnel_instrument;

pub mod prelude {
pub use super::TryCPClientInstrumented as TryCPClient;
}

#[derive(Clone)]
pub struct TryCPClientInstrumented {
trycp_client: TrycpClient,
signal_recv: SignalRecv,
trycp_client: Arc<TrycpClient>,
signal_recv: Arc<tokio::sync::Mutex<SignalRecv>>,
signer: Arc<dyn AgentSigner + Send + Sync>,
reporter: Arc<Reporter>,
timeout: Duration,
}

impl std::fmt::Debug for TryCPClientInstrumented {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TryCPClient").finish()
}
}

mod control_impl {
use super::*;
use trycp_client::Signal;
Expand All @@ -38,16 +47,17 @@ mod control_impl {
{
let (trycp_client, signal_recv) = TrycpClient::connect(request).await?;
Ok(Self {
trycp_client,
signal_recv,
trycp_client: Arc::new(trycp_client),
signal_recv: Arc::new(tokio::sync::Mutex::new(signal_recv)),
signer,
reporter,
timeout: Duration::from_secs(30),
})
}

pub async fn recv_signal(&mut self) -> Option<Signal> {
self.signal_recv.recv().await
let mut recv = self.signal_recv.lock().await;
recv.recv().await
}

/// Given a DNA file, stores the DNA and returns the path at which it is stored.
Expand Down
28 changes: 28 additions & 0 deletions bindings/trycp_runner/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "trycp_wind_tunnel_runner"
version = "0.2.0-alpha.2"
description = "Customises the wind_tunnel_runner for Holochain testing"
license = "MIT"
authors = ["ThetaSinner"]
edition = "2021"
categories = ["development-tools::testing", "development-tools::profiling"]
homepage = "https://github.com/holochain/wind-tunnel"
repository = "https://github.com/holochain/wind-tunnel"

[dependencies]
clap = { workspace = true }
anyhow = { workspace = true }
derive_more = { workspace = true }
serde = { workspace = true }
serde_yaml = { workspace = true }
env_logger = { workspace = true }
log = { workspace = true }

wind_tunnel_runner = { workspace = true }
trycp_client_instrumented = { workspace = true }
holochain_client = { workspace = true }
holochain_types = { workspace = true }
wind_tunnel_core = { workspace = true }

[lints]
workspace = true
74 changes: 74 additions & 0 deletions bindings/trycp_runner/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use clap::Parser;
use serde::Deserialize;
use std::path::PathBuf;
use wind_tunnel_runner::parse_agent_behaviour;
use wind_tunnel_runner::prelude::{ReporterOpt, WindTunnelScenarioCli};

#[derive(Deserialize)]
struct Targets {
nodes: Vec<String>,
}

#[derive(Parser)]
#[command(about, long_about = None)]
pub struct WindTunnelTryCPScenarioCli {
/// Path to the targets file to use.
///
/// Should be a YAML file with a `nodes` field containing a list of TryCP targets.
#[clap(long)]
pub targets: PathBuf,

/// Assign a behaviour to a number of agents. Specify the behaviour and number of agents to assign
/// it to in the format `behaviour:count`. For example `--behaviour=login:5`.
///
/// Specifying the count is optional and will default to 1. This is a useful default if you want to
/// run distributed tests and want a single agent to use a single behaviour on that node.
///
/// You can specify multiple behaviours by using the flag multiple times. For example `--behaviour=add_to_list:5 --behaviour=favourite_items:5`.
///
/// For however many agents you assign to behaviours in total, it must be less than or equal to the total number of agents for this scenario.
/// If it is less than the total number of agents then the remaining agents will be assigned the default behaviour.
///
/// If the configuration is invalid then the scenario will fail to start.
#[clap(long, short, value_parser = parse_agent_behaviour)]
pub behaviour: Vec<(String, usize)>,

/// The number of seconds to run the scenario for
#[clap(long)]
pub duration: Option<u64>,

/// Run this test as a soak test, ignoring any configured duration and continuing to run until stopped
#[clap(long, default_value = "false")]
pub soak: bool,

/// Do not show a progress bar on the CLI.
///
/// This is recommended for CI/CD environments where the progress bar isn't being looked at by anyone and is just adding noise to the logs.
#[clap(long, default_value = "false")]
pub no_progress: bool,

/// The reporter to use.
#[arg(long, value_enum, default_value_t = ReporterOpt::InMemory)]
pub reporter: ReporterOpt,
}

impl TryInto<WindTunnelScenarioCli> for WindTunnelTryCPScenarioCli {
type Error = anyhow::Error;

fn try_into(self) -> Result<WindTunnelScenarioCli, Self::Error> {
let targets = std::fs::read_to_string(&self.targets)?;
let targets: Targets = serde_yaml::from_str(&targets)?;

Ok(WindTunnelScenarioCli {
// Connection string is already forwarded but is supposed to be a single value.
// Pack values together and extract by agent id in helpers.
connection_string: targets.nodes.join(","),
agents: Some(targets.nodes.len()),
behaviour: self.behaviour,
duration: self.duration,
soak: self.soak,
no_progress: self.no_progress,
reporter: self.reporter,
})
}
}
78 changes: 78 additions & 0 deletions bindings/trycp_runner/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use crate::context::TryCPAgentContext;
use crate::runner_context::TryCPRunnerContext;
use std::sync::Arc;
use trycp_client_instrumented::prelude::TryCPClient;
use wind_tunnel_runner::prelude::{AgentContext, HookResult, UserValuesConstraint};

/// Connects to a TryCP server using the current agent index and the list of targets.
///
/// Call this function as follows:
/// ```rust
/// use std::path::Path;
/// use trycp_wind_tunnel_runner::prelude::{AgentContext, connect_trycp_client, HookResult, TryCPAgentContext, TryCPRunnerContext};
///
/// fn agent_setup(ctx: &mut AgentContext<TryCPRunnerContext, TryCPAgentContext>) -> HookResult {
/// connect_trycp_client(ctx)?;
/// Ok(())
/// }
/// ```
pub fn connect_trycp_client<SV: UserValuesConstraint>(
ctx: &mut AgentContext<TryCPRunnerContext, TryCPAgentContext<SV>>,
) -> HookResult {
let agent_index = ctx.agent_index();

let target = ctx
.runner_context()
.get_connection_string()
.split(',')
.nth(agent_index);

// This should never happen because the behaviour assignment should have checked that there were enough agents.
let target = target
.ok_or_else(|| anyhow::anyhow!("Not enough targets to pick a target URL for agent",))?;

let signer = Arc::new(holochain_client::ClientAgentSigner::default());
let reporter = ctx.runner_context().reporter();

let client = ctx.runner_context().executor().execute_in_place({
let signer = signer.clone();
async move { Ok(TryCPClient::connect(target, signer.clone(), reporter).await?) }
})?;

ctx.get_mut().trycp_client = Some(client);
ctx.get_mut().signer = Some(signer);

Ok(())
}

pub fn reset_trycp_remote(
ctx: &mut AgentContext<TryCPRunnerContext, TryCPAgentContext>,
) -> HookResult {
let client = ctx.get().trycp_client();

ctx.runner_context()
.executor()
.execute_in_place(async move {
client.reset(None).await?;
Ok(())
})?;

Ok(())
}

pub fn disconnect_trycp_client<SV: UserValuesConstraint>(
ctx: &mut AgentContext<TryCPRunnerContext, TryCPAgentContext<SV>>,
) -> HookResult {
let client = ctx.get_mut().take_trycp_client();

ctx.runner_context()
.executor()
.execute_in_place(async move {
// The drop implementation requires launching a new task, so drop inside an async context.
// Could also do a `runtime.enter` here but that's deliberately not exposed by the runner.
drop(client);
Ok(())
})?;

Ok(())
}
47 changes: 47 additions & 0 deletions bindings/trycp_runner/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use holochain_client::ClientAgentSigner;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
use trycp_client_instrumented::prelude::TryCPClient;
use trycp_client_instrumented::TryCPClientInstrumented;
use wind_tunnel_runner::prelude::UserValuesConstraint;

#[derive(Debug, Default)]
pub struct DefaultScenarioValues {
pub values: HashMap<String, String>,
}

impl UserValuesConstraint for DefaultScenarioValues {}

/// Holochain-specific context values for the [wind_tunnel_runner::prelude::AgentContext].
#[derive(Default, Debug)]
pub struct TryCPAgentContext<T: UserValuesConstraint = DefaultScenarioValues> {
pub(crate) signer: Option<Arc<ClientAgentSigner>>,
pub(crate) trycp_client: Option<TryCPClient>,
pub scenario_values: T,
}

impl<T: UserValuesConstraint> UserValuesConstraint for TryCPAgentContext<T> {}

impl<T: UserValuesConstraint> TryCPAgentContext<T> {
/// Get the [ClientAgentSigner] that was configured during agent setup.
pub fn signer(&self) -> Arc<ClientAgentSigner> {
self.signer.clone().expect(
"signer is not set, did you forget to call `connect_trycp_client` in your agent_setup?",
)
}

/// Get the [TryCPClient] that was configured during agent setup.
pub fn trycp_client(&self) -> TryCPClient {
self.trycp_client.clone().expect(
"trycp_client is not set, did you forget to call `connect_trycp_client` in your agent_setup?",
)
}

/// Close the TryCP client by dropping it.
///
/// Calling [TryCPAgentContext::trycp_client] after this function, or this function again after, will panic.
pub fn take_trycp_client(&mut self) -> TryCPClientInstrumented {
self.trycp_client.take().expect("trycp_client is not set")
}
}
Loading

0 comments on commit be734ab

Please sign in to comment.