diff --git a/wasmcloud-test-util/Cargo.toml b/wasmcloud-test-util/Cargo.toml index 3214957..6d300fc 100644 --- a/wasmcloud-test-util/Cargo.toml +++ b/wasmcloud-test-util/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmcloud-test-util" -version = "0.6.4" +version = "0.7.0" edition = "2021" authors = [ "wasmcloud Team" ] license = "Apache-2.0" @@ -10,20 +10,20 @@ repository = "https://github.com/wasmcloud/wasmcloud-test" readme = "README.md" [dependencies] -wasmcloud-interface-testing = "0.7.1" -wasmbus-rpc = "0.11.2" +smithy-bindgen = "0.1.0" +wasmbus-rpc = { version = "0.12", features = [ "otel" ] } +serde_bytes = "0.11" regex = "1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] anyhow = "1.0" async-trait = "0.1" -async-nats = "0.23.0" futures = "0.3" -base64 = "0.13" +base64 = "0.21" log = "0.4" nkeys = "0.2.0" serde = { version = "1.0", features=["derive"]} serde_json = "1.0" termcolor = "1.1" tokio = { version = "1", features = ["full"]} -toml = "0.5" +toml = "0.7" diff --git a/wasmcloud-test-util/src/cli.rs b/wasmcloud-test-util/src/cli.rs index 55b4e33..fa07d43 100644 --- a/wasmcloud-test-util/src/cli.rs +++ b/wasmcloud-test-util/src/cli.rs @@ -1,9 +1,9 @@ //! cli utilities use std::io::Write; +use crate::testing::TestResult; use serde::Deserialize; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; -use wasmcloud_interface_testing::TestResult; // structure for deserializing error results #[derive(Deserialize)] diff --git a/wasmcloud-test-util/src/lib.rs b/wasmcloud-test-util/src/lib.rs index 30642f0..6c9b82b 100644 --- a/wasmcloud-test-util/src/lib.rs +++ b/wasmcloud-test-util/src/lib.rs @@ -4,13 +4,71 @@ pub mod provider_test; #[cfg(not(target_arch = "wasm32"))] pub mod cli; -// re-export testing interface -pub use wasmcloud_interface_testing as testing; - // re-export regex and nkeys pub use nkeys; pub use regex; +pub mod testing { + + smithy_bindgen::smithy_bindgen!("testing/testing.smithy", "org.wasmcloud.interface.testing"); + + // after sdk is split we won't have to duplicate this code + impl Default for TestOptions { + fn default() -> TestOptions { + TestOptions { + patterns: vec![".*".to_string()], + options: std::collections::HashMap::default(), + } + } + } + + /// A NamedResult, generated inside a `run_selected!`` or `run_selected_spawn!`` macro, + /// contains a tuple of a test case name and its result. The implementation of From here + /// makes it easy to use the `into()` function to turn the NamedResult into a TestResult. + pub type NamedResult<'name, T> = (&'name str, RpcResult); + + // convert empty RpcResult into a testResult + impl<'name, T: Serialize> From> for TestResult { + fn from((name, res): NamedResult<'name, T>) -> TestResult { + let name = name.into(); + match res { + Ok(res) => { + // test passed. Serialize the data to json + let data = match serde_json::to_vec(&res) { + Ok(v) => serde_json::to_vec(&serde_json::json!({ "data": v })) + .unwrap_or_default(), + // if serialization of data fails, it doesn't change + // the test result, but serialization errors should be logged. + // Logging requires us to have logging set up, but since we might be running as an actor, + // and we can't force the user to add a logging dependency and set up logging, + // so there isn't much we can do here. Not even println!(). + Err(_) => b"".to_vec(), + }; + TestResult { + name, + passed: true, + snap_data: Some(data), + } + } + Err(e) => { + // test failed: generate an error message + let data = serde_json::to_vec(&serde_json::json!( + { + "error": e.to_string(), + } + )) + .ok(); + TestResult { + name, + passed: false, + snap_data: data, + } + } + } + } + } +} + // these macros are supported on all build targets (wasm32 et. al.) /// check that the two expressions are equal, returning RpcError if they are not diff --git a/wasmcloud-test-util/src/provider_test.rs b/wasmcloud-test-util/src/provider_test.rs index 65f1c6a..5f720ac 100644 --- a/wasmcloud-test-util/src/provider_test.rs +++ b/wasmcloud-test-util/src/provider_test.rs @@ -5,13 +5,23 @@ use crate::testing::TestResult; use anyhow::anyhow; use async_trait::async_trait; +use base64::{engine::general_purpose::STANDARD_NO_PAD, engine::Engine}; use futures::future::BoxFuture; use nkeys::{KeyPair, KeyPairType}; use serde::Serialize; -use std::{fs, io::Write, ops::Deref, path::PathBuf, sync::Arc}; +use std::{ + fs, + io::Write, + ops::Deref, + path::{Path, PathBuf}, + process, + sync::{Arc, Mutex}, + time::Duration, +}; use tokio::sync::OnceCell; use toml::value::Value as TomlValue; use wasmbus_rpc::{ + async_nats, common::{Context, Message, SendOpts, Transport}, core::{HealthCheckRequest, HealthCheckResponse, HostData, LinkDefinition, WasmCloudEntity}, error::{RpcError, RpcResult}, @@ -51,7 +61,7 @@ fn to_value_map(data: &toml::map::Map) -> RpcResult, + pub timeout_ms: Mutex, } impl ProviderProcess { + /// Returns the nats topic used by a mock actor + pub fn mock_actor_rpc_topic(&self) -> String { + wasmbus_rpc::rpc_client::rpc_topic(&self.origin(), &self.host_data.lattice_rpc_prefix) + } + /// generate the `origin` field for an Invocation. To the receiving provider, /// the origin field looks like an actor pub fn origin(&self) -> WasmCloudEntity { @@ -210,7 +225,7 @@ impl ProviderProcess { if !resp.is_empty() { eprintln!("shutdown response: {}", String::from_utf8_lossy(&resp)); } - tokio::time::sleep(std::time::Duration::from_secs(2)).await; + tokio::time::sleep(Duration::from_secs(2)).await; Ok(()) } } @@ -222,13 +237,13 @@ impl Transport for Provider { _ctx: &Context, message: Message<'_>, _opts: Option, - ) -> std::result::Result, RpcError> { + ) -> Result, RpcError> { self.inner.send_rpc(message).await } /// sets the time period for an expected response to rpc messages, /// after which an RpcError::Timeout will occur. - fn set_timeout(&self, interval: std::time::Duration) { + fn set_timeout(&self, interval: Duration) { let lock = self.timeout_ms.try_lock(); if let Ok(mut rg) = lock { *rg = interval.as_millis() as u64 @@ -296,7 +311,7 @@ pub fn load_config() -> Result { /// or in the config file as "par_file" pub async fn start_provider_test( config: TomlMap, - exe_path: &std::path::Path, + exe_path: &Path, ld: LinkDefinition, ) -> Result { let exe_file = fs::File::open(exe_path)?; @@ -339,13 +354,13 @@ pub async fn start_provider_test( host_data.structured_logging = false; let buf = serde_json::to_vec(&host_data).map_err(|e| RpcError::Ser(e.to_string()))?; - let mut encoded = base64::encode_config(&buf, base64::STANDARD_NO_PAD); + let mut encoded = STANDARD_NO_PAD.encode(&buf); encoded.push_str("\r\n"); // provider's stdout is piped through our stdout - let mut child_proc = std::process::Command::new(exe_path) - .stdout(std::process::Stdio::piped()) - .stdin(std::process::Stdio::piped()) + let mut child_proc = process::Command::new(exe_path) + .stdout(process::Stdio::piped()) + .stdin(process::Stdio::piped()) .env("RUST_LOG", &log_level) .env("RUST_BACKTRACE", enable_backtrace) .spawn() @@ -379,13 +394,13 @@ pub async fn start_provider_test( rpc_client: RpcClient::new( nats_client, host_key.public_key(), - Some(std::time::Duration::from_millis( + Some(Duration::from_millis( host_data.default_rpc_timeout_ms.unwrap_or(2000), )), Arc::new(host_key), ), host_data, - timeout_ms: std::sync::Mutex::new(DEFAULT_RPC_TIMEOUT_MILLIS), + timeout_ms: Mutex::new(DEFAULT_RPC_TIMEOUT_MILLIS), }), }) } @@ -397,6 +412,7 @@ pub async fn start_provider_test( /// This is like the `run_selected!` macro, except that it spawns /// a thread for running the test case, so it can handle panics /// (and failed assertions, which panic). +/// Users of this macro must have a the testing interface package in scope by using `use wasmcloud_test_util::testing`; #[macro_export] macro_rules! run_selected_spawn { ( $opt:expr, $($tname:ident),* $(,)? ) => {{ @@ -404,7 +420,7 @@ macro_rules! run_selected_spawn { let handle = tokio::runtime::Handle::current(); let all_tests = vec![".*".to_string()]; let pats : &Vec = $opt.patterns.as_ref(); - let mut results: Vec = Vec::new(); + let mut results = Vec::new(); // Each test case regex (pats) is checked against all test names (tname). // This would be simpler to use a RegexSet, but then the tests would @@ -432,7 +448,7 @@ macro_rules! run_selected_spawn { $tname(&opts).await } ).await; - let tr:TestResult = match join { + let tr: wasmcloud_test_util::testing::TestResult = match join { Ok(res) => (name, res).into(), Err(e) => (name, Err::<(),RpcError>( RpcError::Other(format!("join error: {}", e.to_string())) @@ -460,10 +476,11 @@ macro_rules! run_selected_spawn { // all test cases in the current thread (async executor). // The reason I had put the spawn in was to catch panics from assert // calls that fail. +/// Users of this macro must have a the testing interface package in scope by using `use wasmcloud_test_util::testing`; pub async fn run_tests( tests: Vec<(&'static str, TestFunc)>, -) -> std::result::Result, Box> { - let mut results: Vec = Vec::new(); +) -> Result, Box> { + let mut results = Vec::new(); let handle = tokio::runtime::Handle::current(); for (name, tfunc) in tests.into_iter() { let rc: RpcResult<()> = handle.spawn(tfunc()).await?; @@ -543,14 +560,14 @@ pub async fn load_provider() -> Result { } None => DEFAULT_START_DELAY_SEC, }; - tokio::time::sleep(std::time::Duration::from_secs(delay_time_sec)).await; + tokio::time::sleep(Duration::from_secs(delay_time_sec)).await; // optionally, allow extra time to handle put_link if let Some(n) = prov.config.get("link_delay_sec") { if let Some(n) = n.as_integer() { if n > 0 { eprintln!("Pausing {} secs after put_link", n); - tokio::time::sleep(std::time::Duration::from_secs(n as u64)).await; + tokio::time::sleep(Duration::from_secs(n as u64)).await; } } else { return Err(RpcError::InvalidParameter(format!(