diff --git a/.gitignore b/.gitignore index b935c99..02da750 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ **/target/* out.txt +extensions/muxio-ext-test/tests/auto_tests.rs diff --git a/Cargo.lock b/Cargo.lock index ddf466e..f17257e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -838,6 +838,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "muxio-ext-test" +version = "0.10.0-alpha" +dependencies = [ + "bytemuck", + "example-muxio-rpc-service-definition", + "muxio-rpc-service", + "muxio-rpc-service-caller", + "muxio-rpc-service-endpoint", + "muxio-tokio-rpc-client", + "muxio-tokio-rpc-server", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "muxio-rpc-service" version = "0.10.0-alpha" @@ -904,21 +920,15 @@ version = "0.10.0-alpha" dependencies = [ "async-trait", "axum", - "bitcode", - "bytemuck", "bytes", - "example-muxio-rpc-service-definition", "futures-util", "muxio", "muxio-rpc-service", "muxio-rpc-service-caller", "muxio-rpc-service-endpoint", - "muxio-tokio-rpc-client", - "muxio-tokio-rpc-server", "tokio", "tokio-tungstenite", "tracing", - "tracing-subscriber", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d2d3f0c..ca7a05a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ publish.workspace = true # Inherit from workspace [workspace] members = [ ".", + "extensions/muxio-ext-test", "extensions/muxio-rpc-service", "extensions/muxio-rpc-service-caller", "extensions/muxio-rpc-service-endpoint", @@ -49,6 +50,7 @@ muxio-tokio-rpc-client = { path = "extensions/muxio-tokio-rpc-client", version = async-trait = "0.1.88" axum = { version = "0.8.4", features = ["ws"] } bitcode = "0.6.6" +bytemuck = "1.23.1" criterion = { version = "0.6.0" } doc-comment = "0.3.3" bytes = "1.10.1" diff --git a/extensions/muxio-ext-test/Cargo.toml b/extensions/muxio-ext-test/Cargo.toml new file mode 100644 index 0000000..1308239 --- /dev/null +++ b/extensions/muxio-ext-test/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "muxio-ext-test" +authors.workspace = true +version.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true +publish = false + +[dev-dependencies] +bytemuck.workspace = true +tracing-subscriber = { workspace = true, features = ["fmt"] } +muxio-tokio-rpc-client = { workspace = true } +example-muxio-rpc-service-definition.workspace = true +muxio-tokio-rpc-server = { workspace = true } +muxio-rpc-service = { workspace = true } +muxio-rpc-service-caller = { workspace = true } +muxio-rpc-service-endpoint = { workspace = true, features = ["tokio_support"] } +tracing = { workspace = true } +tokio = { workspace = true, features = ["full"] } diff --git a/extensions/muxio-ext-test/README.md b/extensions/muxio-ext-test/README.md new file mode 100644 index 0000000..9a91b68 --- /dev/null +++ b/extensions/muxio-ext-test/README.md @@ -0,0 +1,13 @@ +# Muxio Ext Test + +This crate is intended for testing of crates that would otherwise have circular dependencies (and therefore not publishable). + +## Test discovery + +Any Rust files placed under the `tests/` directory (recursively) will be auto-included +when running `cargo test`. A build script (`build.rs`) generates `tests/auto_tests.rs` +which declares modules for each discovered `*.rs` file. Do not edit `tests/auto_tests.rs` +manually; it is regenerated on test builds. + +If you need to add tests, create `.rs` files under `tests/` (for example `tests/foo/bar.rs`). +The test runner will generate module names automatically. diff --git a/extensions/muxio-ext-test/build.rs b/extensions/muxio-ext-test/build.rs new file mode 100644 index 0000000..3978748 --- /dev/null +++ b/extensions/muxio-ext-test/build.rs @@ -0,0 +1,118 @@ +use std::collections::HashMap; +use std::env; +use std::fs::{self, File}; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; + +fn main() { + let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("missing manifest dir")); + let tests_dir = crate_dir.join("tests"); + + if !tests_dir.is_dir() { + println!( + "cargo:rerun-if-changed={}", + crate_dir.join("tests").display() + ); + return; + } + + let mut discovered = Vec::new(); + collect_test_files(&tests_dir, &tests_dir, &mut discovered) + .expect("failed to scan tests directory"); + discovered.sort(); + + let out_path = tests_dir.join("auto_tests.rs"); + let mut file = File::create(&out_path).expect("failed to create auto_tests.rs"); + + writeln!(file, "// @generated by build.rs. Do not edit.\n") + .expect("failed to write auto_tests header"); + + let mut suffix_counts: HashMap = HashMap::new(); + for rel in &discovered { + if rel.file_name().and_then(|s| s.to_str()) == Some("auto_tests.rs") { + continue; + } + if rel.extension().and_then(|s| s.to_str()) != Some("rs") { + continue; + } + + let rel_str = rel.to_string_lossy().replace('\\', "/"); + let module_name = unique_ident(rel, &mut suffix_counts); + + writeln!(file, "#[allow(non_snake_case)]").expect("failed to write attribute"); + writeln!(file, "mod {} {{", module_name).expect("failed to start module"); + writeln!( + file, + " include!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/tests/{}\"));", + rel_str + ) + .expect("failed to write include"); + writeln!(file, "}}\n").expect("failed to end module"); + } + + println!("cargo:rerun-if-changed={}", tests_dir.display()); + for rel in &discovered { + println!("cargo:rerun-if-changed={}", tests_dir.join(rel).display()); + } +} + +fn collect_test_files(base: &Path, dir: &Path, acc: &mut Vec) -> io::Result<()> { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path + .file_name() + .and_then(|s| s.to_str()) + .map(|s| s.starts_with('.')) + .unwrap_or(false) + { + continue; + } + if path.is_dir() { + collect_test_files(base, &path, acc)?; + } else if let Ok(rel) = path.strip_prefix(base) { + acc.push(rel.to_path_buf()); + } + } + Ok(()) +} + +fn unique_ident(rel: &Path, suffix_counts: &mut HashMap) -> String { + let raw = rel.to_string_lossy(); + let trimmed = raw.strip_suffix(".rs").unwrap_or(&raw); + let mut ident: String = trimmed + .chars() + .map(|c| match c { + 'a'..='z' | 'A'..='Z' | '0'..='9' => c, + _ => '_', + }) + .collect(); + + while ident.contains("__") { + ident = ident.replace("__", "_"); + } + if ident.ends_with('_') { + ident.pop(); + } + if ident.is_empty() { + ident.push_str("test"); + } + if ident + .chars() + .next() + .map(|c| c.is_ascii_digit()) + .unwrap_or(true) + { + ident.insert(0, '_'); + } + + let counter = suffix_counts.entry(ident.clone()).or_insert(0); + if *counter == 0 { + *counter = 1; + ident + } else { + let new_ident = format!("{}_{counter}", ident); + *counter += 1; + new_ident + } +} diff --git a/extensions/muxio-ext-test/src/lib.rs b/extensions/muxio-ext-test/src/lib.rs new file mode 100644 index 0000000..0c58955 --- /dev/null +++ b/extensions/muxio-ext-test/src/lib.rs @@ -0,0 +1 @@ +// no-op library crate used for housing integration tests. diff --git a/extensions/muxio-tokio-rpc-server/tests/proxy_error_propagation_tests.rs b/extensions/muxio-ext-test/tests/muxio-tokio-rpc-server/proxy_error_propagation_tests.rs similarity index 98% rename from extensions/muxio-tokio-rpc-server/tests/proxy_error_propagation_tests.rs rename to extensions/muxio-ext-test/tests/muxio-tokio-rpc-server/proxy_error_propagation_tests.rs index a38a412..6915939 100644 --- a/extensions/muxio-tokio-rpc-server/tests/proxy_error_propagation_tests.rs +++ b/extensions/muxio-ext-test/tests/muxio-tokio-rpc-server/proxy_error_propagation_tests.rs @@ -1,8 +1,8 @@ -//! This integration test verifies error propagation through a proxy server. -//! -//! Scenario: Client A -> Server (Proxy) -> Client B (Provider). -//! When Client B disconnects (crashes) while a call from Client A is pending, -//! the error must propagate back through Server to Client A. +// This integration test verifies error propagation through a proxy server. +// +// Scenario: Client A -> Server (Proxy) -> Client B (Provider). +// When Client B disconnects (crashes) while a call from Client A is pending, +// the error must propagate back through Server to Client A. use example_muxio_rpc_service_definition::prebuffered::Echo; use muxio_rpc_service::{ diff --git a/extensions/muxio-tokio-rpc-client/Cargo.toml b/extensions/muxio-tokio-rpc-client/Cargo.toml index ce319f6..1ef3049 100644 --- a/extensions/muxio-tokio-rpc-client/Cargo.toml +++ b/extensions/muxio-tokio-rpc-client/Cargo.toml @@ -22,6 +22,9 @@ futures = { workspace = true } tracing = { workspace = true } [dev-dependencies] -muxio-tokio-rpc-server = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tokio-tungstenite = { workspace = true } example-muxio-rpc-service-definition = { workspace = true } +futures-util = { workspace = true } axum = { workspace = true } +muxio-tokio-rpc-server = { workspace = true } diff --git a/extensions/muxio-tokio-rpc-server/Cargo.toml b/extensions/muxio-tokio-rpc-server/Cargo.toml index 6b70440..05c9261 100644 --- a/extensions/muxio-tokio-rpc-server/Cargo.toml +++ b/extensions/muxio-tokio-rpc-server/Cargo.toml @@ -20,11 +20,3 @@ muxio-rpc-service = { workspace = true } muxio-rpc-service-endpoint = { workspace = true, features=["tokio_support"] } async-trait = { workspace = true } tracing = { workspace = true } - -[dev-dependencies] -bitcode.workspace = true -bytemuck = "1.23.1" -example-muxio-rpc-service-definition.workspace = true -muxio-tokio-rpc-client.workspace = true -muxio-tokio-rpc-server.workspace = true -tracing-subscriber = { workspace = true, features = ["fmt"] } diff --git a/extensions/muxio-tokio-rpc-server/tests/README.md b/extensions/muxio-tokio-rpc-server/tests/README.md new file mode 100644 index 0000000..fca9f22 --- /dev/null +++ b/extensions/muxio-tokio-rpc-server/tests/README.md @@ -0,0 +1 @@ +Note: `muxio-tokio-rpc-server` tests currently reside in [muxio-ext-test](../../muxio-ext-test/) in order to resolve issues with circular dependencies for publishing.