Skip to content

Commit 4670c85

Browse files
committed
testing: introduce rusty_fork_test_with_proxy!
The macro is similar to rusty_fork_test!, but it also passes a unique IP address to the test function, which can be used to set up one proxy node without risk of address collisions. Without this macro, tests that use test_with_one_proxy() directly and run in subprocesses would sometimes fail due to address collisions with the main process or other subprocesses.
1 parent 707c623 commit 4670c85

File tree

2 files changed

+120
-10
lines changed

2 files changed

+120
-10
lines changed

scylla-rust-wrapper/src/runtime.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,7 @@ impl Runtimes {
125125

126126
#[cfg(test)]
127127
mod tests {
128-
use rusty_fork::rusty_fork_test;
129-
use std::net::SocketAddr;
128+
use std::{net::SocketAddr, process::Command};
130129
use tracing::instrument::WithSubscriber as _;
131130

132131
use scylla_proxy::RunningProxy;
@@ -138,12 +137,12 @@ mod tests {
138137
future::cass_future_free,
139138
session::{cass_session_close, cass_session_connect, cass_session_free, cass_session_new},
140139
testing::{
141-
assert_cass_error_eq, cass_future_wait_check_and_free, mock_init_rules, setup_tracing,
142-
test_with_one_proxy,
140+
assert_cass_error_eq, cass_future_wait_check_and_free, mock_init_rules,
141+
rusty_fork_test_with_proxy, setup_tracing, test_with_one_proxy_at_ip,
143142
},
144143
};
145144

146-
rusty_fork_test! {
145+
rusty_fork_test_with_proxy! {
147146
#![rusty_fork(timeout_ms = 20000)]
148147
#[test]
149148
/// This test aims to verify that the Tokio runtime used by the driver is never dropped
@@ -167,7 +166,7 @@ mod tests {
167166
/// 2. the case is repeated 100 times in a loop, which increases the
168167
/// likelihood of the runtime being dropped from an async context.
169168
/// Without the loop, the test could sometimes pass even without the fix.
170-
fn runtime_is_never_dropped_in_async_context() {
169+
fn runtime_is_never_dropped_in_async_context(ip: IpAddr) {
171170
setup_tracing();
172171

173172
std::panic::set_hook(Box::new(|panic_info| {
@@ -184,9 +183,10 @@ mod tests {
184183
.build()
185184
.unwrap();
186185

187-
runtime.block_on(test_with_one_proxy(
186+
runtime.block_on(test_with_one_proxy_at_ip(
188187
runtime_is_never_dropped_in_async_context_do,
189188
mock_init_rules(),
189+
ip,
190190
)
191191
.with_current_subscriber());
192192
}

scylla-rust-wrapper/src/testing.rs

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ use scylla_proxy::{
22
Condition, Node, Proxy, Reaction as _, RequestFrame, RequestOpcode, RequestReaction,
33
RequestRule, ResponseFrame, RunningProxy,
44
};
5-
use std::{collections::HashMap, ffi::c_char, net::SocketAddr, sync::Arc};
5+
use std::{
6+
collections::HashMap,
7+
ffi::c_char,
8+
net::{IpAddr, SocketAddr},
9+
sync::Arc,
10+
};
611

712
use crate::{
813
argconv::{CMut, CassOwnedSharedPtr, ptr_to_cstr_n},
@@ -111,11 +116,12 @@ pub(crate) fn mock_init_rules() -> impl IntoIterator<Item = RequestRule> {
111116
)))
112117
}
113118

114-
pub(crate) async fn test_with_one_proxy(
119+
pub(crate) async fn test_with_one_proxy_at_ip(
115120
test: impl FnOnce(SocketAddr, RunningProxy) -> RunningProxy + Send + 'static,
116121
rules: impl IntoIterator<Item = RequestRule>,
122+
ip: IpAddr,
117123
) {
118-
let proxy_addr = SocketAddr::new(scylla_proxy::get_exclusive_local_address(), 9042);
124+
let proxy_addr = SocketAddr::new(ip, 9042);
119125

120126
let proxy = Proxy::builder()
121127
.with_node(
@@ -137,3 +143,107 @@ pub(crate) async fn test_with_one_proxy(
137143

138144
let _ = proxy.finish().await;
139145
}
146+
147+
pub(crate) async fn test_with_one_proxy(
148+
test: impl FnOnce(SocketAddr, RunningProxy) -> RunningProxy + Send + 'static,
149+
rules: impl IntoIterator<Item = RequestRule>,
150+
) {
151+
test_with_one_proxy_at_ip(test, rules, scylla_proxy::get_exclusive_local_address()).await
152+
}
153+
154+
/// Run a Rust test in a subprocess, setting up the proxy
155+
/// with a correct unique address.
156+
///
157+
/// This is intended to coordinate in address allocation between
158+
/// the parent process and the subprocesses, so that no address
159+
/// collisions occur when tests are run in multiple processes.
160+
///
161+
/// The basic usage is to simply put this macro around your `#[test]`
162+
/// function and add one argument of type `IpAddr` to it, like so:
163+
///
164+
/// ```rust,nocompile
165+
/// use crate::testing::rusty_fork_test_with_proxy;
166+
///
167+
/// rusty_fork_test_with_proxy! {
168+
/// #[test]
169+
/// fn my_test(ip: IpAddr) {
170+
/// // Use the `ip` variable to set up the proxy.
171+
/// let fut = test_with_one_proxy_at_ip(
172+
/// test_body,
173+
/// proxy_rules,
174+
/// ip,
175+
/// );
176+
///
177+
/// // Run the future to completion.
178+
/// }
179+
/// }
180+
/// ```
181+
///
182+
/// The test will be run in its own process. If the subprocess exits
183+
/// unsuccessfully for any reason, including due to signals, the test fails.
184+
///
185+
/// It is also possible to specify a timeout which is applied to the test in
186+
/// the block, by adding the following line straight before the test:
187+
///
188+
/// ```rust,no_compile
189+
/// #![rusty_fork(timeout_ms = 1000)]
190+
/// ```
191+
///
192+
/// For more details, see the documentation of the `rusty_fork_test` macro
193+
/// in the [rusty_fork] crate.
194+
/// ```
195+
macro_rules! rusty_fork_test_with_proxy {
196+
(#![rusty_fork(timeout_ms = $timeout:expr)]
197+
$(#[$meta:meta])*
198+
fn $test_name:ident($ip:ident: IpAddr) $body:block
199+
) => {
200+
$(#[$meta])*
201+
fn $test_name() {
202+
// Eagerly convert everything to function pointers so that all
203+
// tests use the same instantiation of `fork`.
204+
let supervise_fn = |child: &mut rusty_fork::ChildWrapper, _file: &mut std::fs::File| {
205+
rusty_fork::fork_test::supervise_child(child, $timeout);
206+
};
207+
208+
const ENV_KEY: &str = "PROXY_RUSTY_FORK_IP";
209+
210+
let process_modifier = |child: &mut Command| {
211+
child.env(
212+
ENV_KEY,
213+
scylla_proxy::get_exclusive_local_address().to_string(),
214+
);
215+
};
216+
217+
let test = || {
218+
// Body expects the ip variable in scope.
219+
let $ip: ::std::net::IpAddr = std::env::var(ENV_KEY)
220+
.expect("Parent should have set the proxy address as an env variable.")
221+
.parse()
222+
.expect("Error parsing the proxy address from env variable.");
223+
224+
$body
225+
};
226+
227+
rusty_fork::fork(
228+
rusty_fork::rusty_fork_test_name!($test_name),
229+
rusty_fork::rusty_fork_id!(),
230+
process_modifier,
231+
supervise_fn,
232+
test,
233+
)
234+
.expect("forking test failed")
235+
}
236+
};
237+
238+
(
239+
$(#[$meta:meta])*
240+
fn $test_name:ident() $body:block
241+
) => {
242+
rusty_fork_test! {
243+
#![rusty_fork(timeout_ms = 0)]
244+
245+
$(#[$meta])* fn $test_name() $body
246+
}
247+
};
248+
}
249+
pub(crate) use rusty_fork_test_with_proxy;

0 commit comments

Comments
 (0)