|
1 | 1 | use std::ffi::{CString, c_char};
|
| 2 | +use std::fmt::Write as _; |
2 | 3 | use std::net::SocketAddr;
|
3 | 4 | use std::sync::Arc;
|
4 | 5 | use std::time::Duration;
|
@@ -248,6 +249,85 @@ pub unsafe extern "C" fn testing_statement_set_recording_history_listener(
|
248 | 249 | statement.record_hosts = enable != 0;
|
249 | 250 | }
|
250 | 251 |
|
| 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 | + |
251 | 331 | /// A retry policy that always ignores all errors.
|
252 | 332 | ///
|
253 | 333 | /// Useful for testing purposes.
|
|
0 commit comments