#![allow(improper_ctypes_definitions)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::all)]
#![allow(unused_unsafe)]
#![allow(unused_variables)]
#![doc = include_str!("../README.md")]
mod arg_classifier;
mod common;
mod generator;
mod parser;
pub mod test_logger;
pub use arg_classifier::*;
pub use common::*;
pub use generator::*;
pub use parser::*;
use proc_macro2::TokenStream;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
pub const CUSTOM_AERON_CODE: &str = include_str!("./aeron_custom.rs");
pub const CUSTOM_ARCHIVE_CODE: &str = include_str!("./aeron_custom_archive.rs");
pub const COMMON_CODE: &str = include_str!("./common.rs");
#[cfg(test)]
const TRYBUILD_ARCHIVE_ERROR_STUB: &str = r#"
#[derive(Debug, Clone)]
pub struct AeronArchiveError { pub code: i32, pub message: String }
impl AeronArchiveError { pub fn from_code(code: i32) -> Self { Self { code, message: String::new() } } }
impl std::fmt::Display for AeronArchiveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "archive error {}", self.code) }
}
impl std::error::Error for AeronArchiveError {}
"#;
pub fn append_to_file(file_path: &str, code: &str) -> std::io::Result<()> {
let path = Path::new(file_path);
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent)?;
}
}
let mut file = OpenOptions::new().create(true).write(true).append(true).open(path)?;
writeln!(file, "\n{}", code)?;
Ok(())
}
#[allow(dead_code)]
pub fn format_with_rustfmt(code: &str) -> Result<String, std::io::Error> {
let mut rustfmt = Command::new("rustfmt")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
if let Some(mut stdin) = rustfmt.stdin.take() {
stdin.write_all(code.as_bytes())?;
}
let output = rustfmt.wait_with_output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("rustfmt failed: {}", stderr),
));
}
let formatted_code = String::from_utf8_lossy(&output.stdout).to_string();
if !code.trim().is_empty() && formatted_code.trim().is_empty() {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"rustfmt produced empty output",
))
} else {
Ok(formatted_code)
}
}
#[allow(dead_code)]
pub fn format_token_stream(tokens: TokenStream) -> String {
let code = tokens.to_string();
match format_with_rustfmt(&code) {
Ok(formatted_code) if !formatted_code.trim().is_empty() => formatted_code,
_ => code.replace("{", "{\n"), }
}
#[cfg(test)]
mod tests {
use crate::generator::MEDIA_DRIVER_BINDINGS;
use crate::parser::parse_bindings;
use crate::{
append_to_file, format_token_stream, format_with_rustfmt, ARCHIVE_BINDINGS, CLIENT_BINDINGS, CUSTOM_AERON_CODE,
TRYBUILD_ARCHIVE_ERROR_STUB,
};
use proc_macro2::TokenStream;
use std::fs;
fn running_under_valgrind() -> bool {
std::env::var_os("RUSTERON_VALGRIND").is_some()
}
#[test]
#[cfg(not(target_os = "windows"))] fn client() {
if running_under_valgrind() {
return;
}
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("bindings")
.join("client.rs");
let mut bindings = parse_bindings(&path);
assert_eq!(
"AeronImageFragmentAssembler",
bindings
.wrappers
.get("aeron_image_fragment_assembler_t")
.unwrap()
.class_name
);
assert_eq!(
0,
bindings.methods.len(),
"expected all methods to have been matched {:#?}",
bindings.methods
);
let file = write_to_file(TokenStream::new(), true, "client.rs");
let bindings_copy = bindings.clone();
for handler in bindings.handlers.iter_mut() {
let _ = crate::generate_handlers(handler, &bindings_copy);
}
for (p, w) in bindings.wrappers.values().enumerate() {
let code = crate::generate_rust_code(w, &bindings.wrappers, p == 0, true, true, &bindings.handlers);
write_to_file(code, false, "client.rs");
}
let bindings_copy = bindings.clone();
for handler in bindings.handlers.iter_mut() {
let code = crate::generate_handlers(handler, &bindings_copy);
append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
}
let t = trybuild::TestCases::new();
append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
append_to_file(&file, CLIENT_BINDINGS).unwrap();
append_to_file(&file, "}").unwrap();
append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
append_to_file(&file, "\npub fn main() {}\n").unwrap();
t.pass(file)
}
#[test]
#[cfg(not(target_os = "windows"))] fn media_driver() {
if running_under_valgrind() {
return;
}
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("bindings")
.join("media-driver.rs");
let mut bindings = parse_bindings(&path);
assert_eq!(
"AeronImageFragmentAssembler",
bindings
.wrappers
.get("aeron_image_fragment_assembler_t")
.unwrap()
.class_name
);
let file = write_to_file(TokenStream::new(), true, "md.rs");
let bindings_copy = bindings.clone();
for handler in bindings.handlers.iter_mut() {
let _ = crate::generate_handlers(handler, &bindings_copy);
}
for (p, w) in bindings
.wrappers
.values()
.filter(|w| !w.type_name.contains("pthread") && !w.type_name.contains("_t_"))
.enumerate()
{
let code = crate::generate_rust_code(w, &bindings.wrappers, p == 0, true, true, &bindings.handlers);
write_to_file(code, false, "md.rs");
}
let bindings_copy = bindings.clone();
for handler in bindings.handlers.iter_mut() {
let code = crate::generate_handlers(handler, &bindings_copy);
append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
}
let t = trybuild::TestCases::new();
append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
append_to_file(&file, MEDIA_DRIVER_BINDINGS).unwrap();
append_to_file(&file, "}").unwrap();
append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
append_to_file(&file, "\npub fn main() {}\n").unwrap();
t.pass(&file)
}
#[test]
#[cfg(not(target_os = "windows"))] fn archive() {
if running_under_valgrind() {
return;
}
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("bindings")
.join("archive.rs");
let mut bindings = parse_bindings(&path);
assert_eq!(
"AeronImageFragmentAssembler",
bindings
.wrappers
.get("aeron_image_fragment_assembler_t")
.unwrap()
.class_name
);
let file = write_to_file(TokenStream::new(), true, "archive.rs");
let bindings_copy = bindings.clone();
for handler in bindings.handlers.iter_mut() {
let _ = crate::generate_handlers(handler, &bindings_copy);
}
for (p, w) in bindings.wrappers.values().enumerate() {
let code = crate::generate_rust_code(w, &bindings.wrappers, p == 0, true, true, &bindings.handlers);
write_to_file(code, false, "archive.rs");
}
let bindings_copy = bindings.clone();
for handler in bindings.handlers.iter_mut() {
let code = crate::generate_handlers(handler, &bindings_copy);
append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
}
let t = trybuild::TestCases::new();
append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
append_to_file(&file, ARCHIVE_BINDINGS).unwrap();
append_to_file(&file, "}").unwrap();
append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
append_to_file(&file, TRYBUILD_ARCHIVE_ERROR_STUB).unwrap();
append_to_file(&file, "\npub fn main() {}\n").unwrap();
t.pass(file)
}
fn assert_aeron_rs_snapshot_matches(
bindings_file: &str,
snapshot_rel_path: &str,
skip_filter: fn(&str) -> bool,
extra_custom_code: &[&str],
) {
if running_under_valgrind() {
return;
}
let bindings_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("bindings")
.join(bindings_file);
let snapshot_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(snapshot_rel_path);
let mut bindings = crate::parse_bindings_with_custom(&bindings_path, extra_custom_code);
let bindings_copy = bindings.clone();
for handler in bindings.handlers.iter_mut() {
let _ = crate::generate_handlers(handler, &bindings_copy);
}
let mut stream = TokenStream::new();
for (p, w) in bindings
.wrappers
.values()
.filter(|w| skip_filter(&w.type_name))
.enumerate()
{
let code = crate::generate_rust_code(
w,
&bindings.wrappers,
p == 0,
false, true,
&bindings.handlers,
);
stream.extend(code);
}
let bindings_copy = bindings.clone();
for handler in bindings.handlers.iter_mut() {
let code = crate::generate_handlers(handler, &bindings_copy);
stream.extend(code);
}
let generated = format_with_rustfmt(&stream.to_string())
.unwrap_or_else(|_| panic!("rustfmt failed on regenerated {bindings_file}"));
let committed = fs::read_to_string(&snapshot_path)
.unwrap_or_else(|_| panic!("missing committed snapshot: {}", snapshot_path.display()));
let norm = |s: &str| s.trim().to_string();
let (generated, committed) = (norm(&generated), norm(&committed));
if generated != committed {
let mut line_no = 0;
for (i, (g, c)) in generated.lines().zip(committed.lines()).enumerate() {
if g != c {
line_no = i + 1;
break;
}
}
if line_no == 0 {
line_no = generated.lines().count().min(committed.lines().count()) + 1;
}
panic!(
"docs-rs snapshot drift in {}: generated aeron.rs differs from committed \
{} (first divergence near line {}). GENERATED first 3 lines:\n{}\n\
COMMITTED first 3 lines:\n{}\nRebuild with `COPY_BINDINGS=true` \
(e.g. `just build`) and commit the regenerated snapshot.",
bindings_file,
snapshot_path.display(),
line_no,
generated.lines().take(3).collect::<Vec<_>>().join("\n"),
committed.lines().take(3).collect::<Vec<_>>().join("\n"),
);
}
}
#[test]
#[cfg(not(target_os = "windows"))]
fn client_aeron_rs_matches_committed_snapshot() {
assert_aeron_rs_snapshot_matches(
"client.rs",
"../rusteron-client/docs-rs/aeron.rs",
|t| t.starts_with("aeron_"),
&[],
);
}
#[test]
#[cfg(not(target_os = "windows"))]
fn archive_aeron_rs_matches_committed_snapshot() {
assert_aeron_rs_snapshot_matches(
"archive.rs",
"../rusteron-archive/docs-rs/aeron.rs",
|t| t.starts_with("aeron_"),
&[crate::CUSTOM_ARCHIVE_CODE],
);
}
#[test]
#[cfg(not(target_os = "windows"))]
fn media_driver_aeron_rs_matches_committed_snapshot() {
assert_aeron_rs_snapshot_matches(
"media-driver.rs",
"../rusteron-media-driver/docs-rs/aeron.rs",
|t| !t.contains("pthread") && !t.contains("_t_"),
&[],
);
}
#[test]
fn aeron_custom_rs_snapshots_match_source() {
let no_extra: &[&str] = &[];
for (crate_name, extra) in [
("client", no_extra),
("archive", &[crate::CUSTOM_ARCHIVE_CODE][..]),
("media-driver", no_extra),
] {
let mut source = format!("\n{}\n", CUSTOM_AERON_CODE);
for code in extra {
source.push_str(&format!("\n{}\n", code));
}
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join(format!("../rusteron-{crate_name}/docs-rs/aeron_custom.rs"));
let committed = fs::read_to_string(&path)
.unwrap_or_else(|_| panic!("missing committed aeron_custom.rs: {}", path.display()));
assert_eq!(
committed.trim(),
source.trim(),
"rusteron-{crate_name}/docs-rs/aeron_custom.rs drifted from \
rusteron-code-gen/src/aeron_custom*.rs. Rebuild with `COPY_BINDINGS=true`.",
);
}
}
fn write_to_file(rust_code: TokenStream, delete: bool, name: &str) -> String {
let src = format_token_stream(rust_code);
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!("../target/{}", name));
let path = path.to_str().unwrap();
if delete {
let _ = fs::remove_file(path);
}
append_to_file(path, &src).unwrap();
path.to_string()
}
}
#[cfg(test)]
mod test {
use crate::{CResource, CleanupBox, ManagedCResource};
use crate::common::RcOrArc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[allow(unused_imports)]
use RcOrArc as Rc;
fn make_resource(val: i32) -> *mut i32 {
Box::into_raw(Box::new(val))
}
unsafe fn reclaim_resource(ptr: *mut i32) {
if !ptr.is_null() {
let _ = Box::from_raw(ptr);
}
}
#[test]
fn test_drop_calls_cleanup_non_borrowed_no_cleanup_struct() {
let flag = Arc::new(AtomicBool::new(false));
let flag_clone = flag.clone();
let resource_ptr = make_resource(10);
let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
flag_clone.store(true, Ordering::SeqCst);
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
}) as CleanupBox<i32>);
{
let _resource = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = resource_ptr;
}
0
},
cleanup,
false,
);
assert!(_resource.is_ok())
}
assert!(flag.load(Ordering::SeqCst));
}
#[test]
fn test_drop_calls_cleanup_non_borrowed_with_cleanup_struct() {
let flag = Arc::new(AtomicBool::new(false));
let flag_clone = flag.clone();
let resource_ptr = make_resource(20);
let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
flag_clone.store(true, Ordering::SeqCst);
unsafe {
*res = std::ptr::null_mut();
}
0
}) as CleanupBox<i32>);
{
let _resource = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = resource_ptr;
}
0
},
cleanup,
true,
);
assert!(_resource.is_ok())
}
assert!(flag.load(Ordering::SeqCst));
}
#[test]
fn test_drop_does_not_call_cleanup_if_already_closed() {
let flag = Arc::new(AtomicBool::new(false));
let flag_clone = flag.clone();
let resource_ptr = make_resource(30);
let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
flag_clone.store(true, Ordering::SeqCst);
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
}) as CleanupBox<i32>);
let resource = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = resource_ptr;
}
0
},
cleanup,
false,
);
assert!(resource.is_ok());
if let Ok(ref resource) = resource {
assert!(resource.close_shared().is_ok());
}
flag.store(false, Ordering::SeqCst);
drop(resource);
assert!(!flag.load(Ordering::SeqCst));
}
#[test]
fn close_resource_closes_shared_resource_once_across_clones() {
let close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
let close_count_for_cleanup = close_count.clone();
let resource_ptr = make_resource(40);
let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
}) as CleanupBox<i32>);
let resource = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = resource_ptr;
}
0
},
cleanup,
false,
);
assert!(resource.is_ok());
let resource = resource.ok().unwrap();
let inner = Rc::new(resource);
let handle_one = CResource::OwnedOnHeap(inner.clone());
let handle_two = CResource::OwnedOnHeap(inner);
assert!(!handle_one.get().is_null());
assert!(!handle_two.get().is_null());
assert!(handle_one.close_resource().is_ok());
assert_eq!(1, close_count.load(Ordering::SeqCst));
assert!(handle_one.get().is_null());
assert!(handle_two.get().is_null());
assert!(handle_two.close_resource().is_ok());
assert_eq!(1, close_count.load(Ordering::SeqCst));
}
#[test]
fn close_resource_defers_owner_close_while_dependent_is_alive() {
let close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
let close_count_for_cleanup = close_count.clone();
let owner_ptr = make_resource(60);
let child_ptr = make_resource(61);
let owner_cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
}) as CleanupBox<i32>);
let owner = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = owner_ptr;
}
0
},
owner_cleanup,
false,
);
assert!(owner.is_ok());
let owner = CResource::OwnedOnHeap(Rc::new(owner.ok().unwrap()));
let child = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = child_ptr;
}
0
},
Some(Box::new(move |res| {
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
})),
false,
);
assert!(child.is_ok());
let child = CResource::OwnedOnHeap(Rc::new(child.ok().unwrap()));
child.add_dependency(owner.clone());
assert!(owner.close_resource_deferred_if_shared().is_ok());
assert_eq!(0, close_count.load(Ordering::SeqCst));
assert!(!owner.get().is_null());
drop(owner);
drop(child);
assert_eq!(1, close_count.load(Ordering::SeqCst));
}
#[test]
fn close_resource_deferral_is_retryable_if_owner_close_fails_after_dependent_drops() {
let close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
let close_count_for_cleanup = close_count.clone();
let owner_ptr = make_resource(70);
let child_ptr = make_resource(71);
let owner_cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
let count = close_count_for_cleanup.fetch_add(1, Ordering::SeqCst) + 1;
if count == 1 {
return -1;
}
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
}) as CleanupBox<i32>);
let owner = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = owner_ptr;
}
0
},
owner_cleanup,
false,
);
assert!(owner.is_ok());
let owner = CResource::OwnedOnHeap(Rc::new(owner.ok().unwrap()));
let owner_retry_handle = owner.clone();
let child = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = child_ptr;
}
0
},
Some(Box::new(move |res| {
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
})),
false,
);
assert!(child.is_ok());
let child = CResource::OwnedOnHeap(Rc::new(child.ok().unwrap()));
child.add_dependency(owner.clone());
assert!(owner.close_resource_deferred_if_shared().is_ok());
drop(owner);
drop(child);
assert_eq!(0, close_count.load(Ordering::SeqCst));
assert!(!owner_retry_handle.get().is_null());
assert!(owner_retry_handle.close_resource_deferred_if_shared().is_err());
assert_eq!(1, close_count.load(Ordering::SeqCst));
assert!(!owner_retry_handle.get().is_null());
assert!(owner_retry_handle.close_resource_deferred_if_shared().is_ok());
assert_eq!(2, close_count.load(Ordering::SeqCst));
assert!(owner_retry_handle.get().is_null());
}
#[test]
fn close_resource_propagates_failure_and_allows_retry() {
let close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
let close_count_for_cleanup = close_count.clone();
let resource_ptr = make_resource(50);
let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
let count = close_count_for_cleanup.fetch_add(1, Ordering::SeqCst) + 1;
if count == 1 {
return -1;
}
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
}) as CleanupBox<i32>);
let resource = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = resource_ptr;
}
0
},
cleanup,
false,
);
assert!(resource.is_ok());
let resource = resource.ok().unwrap();
let handle = CResource::OwnedOnHeap(Rc::new(resource));
assert!(handle.close_resource().is_err());
assert_eq!(1, close_count.load(Ordering::SeqCst));
assert!(!handle.get().is_null());
assert!(handle.close_resource().is_ok());
assert_eq!(2, close_count.load(Ordering::SeqCst));
assert!(handle.get().is_null());
}
#[test]
fn close_resource_with_uses_custom_cleanup_once_across_clones() {
let default_close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
let custom_close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
let default_close_count_for_cleanup = default_close_count.clone();
let resource_ptr = make_resource(80);
let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
default_close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
}) as CleanupBox<i32>);
let resource = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = resource_ptr;
}
0
},
cleanup,
false,
);
assert!(resource.is_ok());
let inner = Rc::new(resource.ok().unwrap());
let handle_one = CResource::OwnedOnHeap(inner.clone());
let handle_two = CResource::OwnedOnHeap(inner);
let custom_close_count_for_cleanup = custom_close_count.clone();
assert!(handle_one
.close_resource_with(move |res| {
custom_close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
})
.is_ok());
assert_eq!(0, default_close_count.load(Ordering::SeqCst));
assert_eq!(1, custom_close_count.load(Ordering::SeqCst));
assert!(handle_one.get().is_null());
assert!(handle_two.get().is_null());
assert!(handle_two.close_resource().is_ok());
assert_eq!(0, default_close_count.load(Ordering::SeqCst));
assert_eq!(1, custom_close_count.load(Ordering::SeqCst));
}
#[test]
fn close_resource_with_failure_restores_default_cleanup_for_retry() {
let default_close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
let custom_close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
let default_close_count_for_cleanup = default_close_count.clone();
let resource_ptr = make_resource(90);
let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
default_close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
unsafe {
reclaim_resource(*res);
*res = std::ptr::null_mut();
}
0
}) as CleanupBox<i32>);
let resource = ManagedCResource::new(
|res: *mut *mut i32| {
unsafe {
*res = resource_ptr;
}
0
},
cleanup,
false,
);
assert!(resource.is_ok());
let handle = CResource::OwnedOnHeap(Rc::new(resource.ok().unwrap()));
let custom_close_count_for_cleanup = custom_close_count.clone();
assert!(handle
.close_resource_with(move |_res| {
custom_close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
-1
})
.is_err());
assert_eq!(0, default_close_count.load(Ordering::SeqCst));
assert_eq!(1, custom_close_count.load(Ordering::SeqCst));
assert!(!handle.get().is_null());
assert!(handle.close_resource().is_ok());
assert_eq!(1, default_close_count.load(Ordering::SeqCst));
assert_eq!(1, custom_close_count.load(Ordering::SeqCst));
assert!(handle.get().is_null());
}
#[test]
fn close_resource_is_noop_for_stack_and_borrowed_resources() {
let mut borrowed_value = 10;
let borrowed = CResource::Borrowed(&mut borrowed_value as *mut i32);
assert!(borrowed.close_resource().is_ok());
assert_eq!(10, borrowed_value);
let stack = CResource::OwnedOnStack(std::mem::MaybeUninit::new(20));
assert!(stack.close_resource().is_ok());
assert_eq!(20, unsafe { *stack.get() });
}
}