Skip to content

Commit 0471a6a

Browse files
committed
IT: implement testing_future_get_attempted_hosts
This commit implements the function `testing_future_get_attempted_hosts` in Rust, as well as includes a test for the new function. It's going to be used in the C++ integration tests. I had tough time figuring out how to return a list of attempted hosts' addresses to C code, so I decided to return a concatenated string of them in a stringified form, delimited by '\n'. The overhead this introduces is not important at all, as these are just integration tests. The caller is responsible for freeing the memory. They must do so by calling `testing_free_cstring`, because the string is allocated in Rust. The new unit test, unlike all unit tests so far, is put behind the #[cfg(cpp_integration_testing)] attribute. In order to run it, we must to supply the `cpp_integration_testing` flag to RUSTFLAGS. So far, RUSTFLAGS were configured in .cargo/config.toml, in `[build]`. As this method of setting RUSTFLAGS does not allow setting different flags for test target and for the rest of the build, I decided to set the necessary RUSTFLAGS for tests in the Makefile. Keep in mind that this will require us to keep the flags there and in the `.cargo/config.toml` in sync, unfortunately.
1 parent 5d3fa0c commit 0471a6a

File tree

2 files changed

+81
-1
lines changed

2 files changed

+81
-1
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,4 @@ endif
292292
build/cassandra-integration-tests --version=${CASSANDRA_VERSION} --category=CASSANDRA --verbose=ccm --gtest_filter="${CASSANDRA_NO_VALGRIND_TEST_FILTER}"
293293

294294
run-test-unit: install-cargo-if-missing _update-rust-tooling
295-
@cd ${CURRENT_DIR}/scylla-rust-wrapper; cargo test
295+
@cd ${CURRENT_DIR}/scylla-rust-wrapper; RUSTFLAGS="--cfg cpp_rust_unstable --cfg cpp_integration_testing" cargo test

scylla-rust-wrapper/src/integration_testing.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::ffi::{CString, c_char};
2+
use std::fmt::Write as _;
23
use std::net::SocketAddr;
34
use std::sync::Arc;
45
use std::time::Duration;
@@ -248,6 +249,85 @@ pub unsafe extern "C" fn testing_statement_set_recording_history_listener(
248249
statement.record_hosts = enable != 0;
249250
}
250251

252+
/// Retrieves hosts that were attempted during the execution of a future.
253+
/// In order for this to work, the statement must have been configured with
254+
/// `testing_statement_set_recording_history_listener`.
255+
#[unsafe(no_mangle)]
256+
pub unsafe extern "C" fn testing_future_get_attempted_hosts(
257+
future_raw: CassBorrowedSharedPtr<CassFuture, CMut>,
258+
) -> *mut c_char {
259+
// This function should return a list of attempted hosts.
260+
// Care should be taken to ensure that the list is properly allocated and freed.
261+
// The problem is that the return type must be understandable by C code.
262+
// See testing.cpp:53 (get_attempted_hosts_from_future()) for an example of how
263+
// this is done in C++.
264+
//
265+
// Idea: Create a concatenated string of attempted hosts.
266+
// Return a pointer to that string. Caller is responsible for freeing it.
267+
268+
let future: &CassFuture = ArcFFI::as_ref(future_raw).unwrap();
269+
270+
let attempted_hosts = future.attempted_hosts();
271+
let concatenated_hosts = attempted_hosts.iter().fold(String::new(), |mut acc, host| {
272+
// Convert the SocketAddr to a string.
273+
// Delimit address strings with '\n' to enable easy parsing in C.
274+
275+
write!(&mut acc, "{}\n", host.ip()).unwrap();
276+
acc
277+
});
278+
279+
// The caller is responsible for freeing this memory, by calling `testing_free_cstring`.
280+
unsafe { CString::from_vec_unchecked(concatenated_hosts.into_bytes()) }.into_raw()
281+
}
282+
283+
/// Ensures that the `testing_future_get_attempted_hosts` function
284+
/// behaves correctly, i.e., it returns a list of attempted hosts as a concatenated string.
285+
#[test]
286+
fn test_future_get_attempted_hosts() {
287+
use scylla::observability::history::HistoryListener as _;
288+
289+
let listener = Arc::new(RecordingHistoryListener::new());
290+
let future = CassFuture::new_from_future(std::future::pending(), Some(listener.clone()));
291+
292+
fn assert_attempted_hosts_eq(future: &Arc<CassFuture>, hosts: &[String]) {
293+
let hosts_str = unsafe { testing_future_get_attempted_hosts(ArcFFI::as_ptr(future)) };
294+
let hosts_cstr = unsafe { CString::from_raw(hosts_str) };
295+
let hosts_string = hosts_cstr.to_str().unwrap();
296+
297+
// Split the string by '\n' and collect into a Vec<&str>.
298+
let attempted_hosts: Vec<&str> =
299+
hosts_string.split('\n').filter(|s| !s.is_empty()).collect();
300+
301+
assert_eq!(attempted_hosts, hosts);
302+
}
303+
304+
// 1. Test with no attempted hosts.
305+
{
306+
assert_attempted_hosts_eq(&future, &[]);
307+
}
308+
309+
let addr1: SocketAddr = SocketAddr::from(([127, 0, 0, 1], 9042));
310+
let addr2: SocketAddr = SocketAddr::from(([127, 0, 0, 2], 9042));
311+
312+
// 2. Attempt two hosts and see if they are recorded correctly, in order.
313+
{
314+
listener.log_attempt_start(RequestId(0), None, addr1);
315+
listener.log_attempt_start(RequestId(0), None, addr2);
316+
assert_attempted_hosts_eq(&future, &[addr1, addr2].map(|addr| addr.ip().to_string()))
317+
}
318+
319+
let addr3: SocketAddr = SocketAddr::from(([127, 0, 0, 3], 9042));
320+
321+
// 3. Attempt one more host and see if all hosts are present, in order.
322+
{
323+
listener.log_attempt_start(RequestId(0), None, addr3);
324+
assert_attempted_hosts_eq(
325+
&future,
326+
&[addr1, addr2, addr3].map(|addr| addr.ip().to_string()),
327+
)
328+
}
329+
}
330+
251331
/// A retry policy that always ignores all errors.
252332
///
253333
/// Useful for testing purposes.

0 commit comments

Comments
 (0)