1#![allow(improper_ctypes_definitions)]
2#![allow(non_upper_case_globals)]
3#![allow(non_camel_case_types)]
4#![allow(non_snake_case)]
5#![allow(clippy::all)]
6#![allow(unused_unsafe)]
7#![allow(unused_variables)]
8#![doc = include_str!("../README.md")]
9
10mod arg_classifier;
11mod common;
12mod generator;
13mod parser;
14pub mod test_logger;
15
16pub use arg_classifier::*;
17pub use common::*;
18pub use generator::*;
19pub use parser::*;
20
21use proc_macro2::TokenStream;
22use std::fs::OpenOptions;
23use std::io::Write;
24use std::path::Path;
25use std::process::{Command, Stdio};
26
27pub const CUSTOM_AERON_CODE: &str = include_str!("./aeron_custom.rs");
28pub const CUSTOM_ARCHIVE_CODE: &str = include_str!("./aeron_custom_archive.rs");
29pub const COMMON_CODE: &str = include_str!("./common.rs");
30
31#[cfg(test)]
38const TRYBUILD_ARCHIVE_ERROR_STUB: &str = r#"
39 #[derive(Debug, Clone)]
40 pub struct AeronArchiveError { pub code: i32, pub message: String }
41 impl AeronArchiveError { pub fn from_code(code: i32) -> Self { Self { code, message: String::new() } } }
42 impl std::fmt::Display for AeronArchiveError {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "archive error {}", self.code) }
44 }
45 impl std::error::Error for AeronArchiveError {}
46"#;
47
48pub fn append_to_file(file_path: &str, code: &str) -> std::io::Result<()> {
49 let path = Path::new(file_path);
50 if let Some(parent) = path.parent() {
51 if !parent.as_os_str().is_empty() {
52 std::fs::create_dir_all(parent)?;
53 }
54 }
55
56 let mut file = OpenOptions::new().create(true).write(true).append(true).open(path)?;
57
58 writeln!(file, "\n{}", code)?;
59
60 Ok(())
61}
62
63#[allow(dead_code)]
64pub fn format_with_rustfmt(code: &str) -> Result<String, std::io::Error> {
65 let mut rustfmt = Command::new("rustfmt")
66 .stdin(Stdio::piped())
67 .stdout(Stdio::piped())
68 .spawn()?;
69
70 if let Some(mut stdin) = rustfmt.stdin.take() {
71 stdin.write_all(code.as_bytes())?;
72 }
73
74 let output = rustfmt.wait_with_output()?;
75
76 if !output.status.success() {
77 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
78 return Err(std::io::Error::new(
79 std::io::ErrorKind::Other,
80 format!("rustfmt failed: {}", stderr),
81 ));
82 }
83
84 let formatted_code = String::from_utf8_lossy(&output.stdout).to_string();
85
86 if !code.trim().is_empty() && formatted_code.trim().is_empty() {
89 Err(std::io::Error::new(
90 std::io::ErrorKind::Other,
91 "rustfmt produced empty output",
92 ))
93 } else {
94 Ok(formatted_code)
95 }
96}
97
98#[allow(dead_code)]
99pub fn format_token_stream(tokens: TokenStream) -> String {
100 let code = tokens.to_string();
101
102 match format_with_rustfmt(&code) {
103 Ok(formatted_code) if !formatted_code.trim().is_empty() => formatted_code,
104 _ => code.replace("{", "{\n"), }
106}
107
108#[cfg(test)]
109mod tests {
110 use crate::generator::MEDIA_DRIVER_BINDINGS;
111 use crate::parser::parse_bindings;
112 use crate::{
113 append_to_file, format_token_stream, format_with_rustfmt, ARCHIVE_BINDINGS, CLIENT_BINDINGS, CUSTOM_AERON_CODE,
114 TRYBUILD_ARCHIVE_ERROR_STUB,
115 };
116 use proc_macro2::TokenStream;
117 use std::fs;
118
119 fn running_under_valgrind() -> bool {
122 std::env::var_os("RUSTERON_VALGRIND").is_some()
123 }
124
125 #[test]
126 #[cfg(not(target_os = "windows"))] fn client() {
128 if running_under_valgrind() {
129 return;
130 }
131
132 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
133 .join("bindings")
134 .join("client.rs");
135 let mut bindings = parse_bindings(&path);
136 assert_eq!(
137 "AeronImageFragmentAssembler",
138 bindings
139 .wrappers
140 .get("aeron_image_fragment_assembler_t")
141 .unwrap()
142 .class_name
143 );
144 assert_eq!(
145 0,
146 bindings.methods.len(),
147 "expected all methods to have been matched {:#?}",
148 bindings.methods
149 );
150
151 let file = write_to_file(TokenStream::new(), true, "client.rs");
152 let bindings_copy = bindings.clone();
153 for handler in bindings.handlers.iter_mut() {
154 let _ = crate::generate_handlers(handler, &bindings_copy);
156 }
157 for (p, w) in bindings.wrappers.values().enumerate() {
158 let code = crate::generate_rust_code(w, &bindings.wrappers, p == 0, true, true, &bindings.handlers);
159 write_to_file(code, false, "client.rs");
160 }
161 let bindings_copy = bindings.clone();
162 for handler in bindings.handlers.iter_mut() {
163 let code = crate::generate_handlers(handler, &bindings_copy);
164 append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
165 }
166
167 let t = trybuild::TestCases::new();
168 append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
169 append_to_file(&file, CLIENT_BINDINGS).unwrap();
170 append_to_file(&file, "}").unwrap();
171 append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
172 append_to_file(&file, "\npub fn main() {}\n").unwrap();
173 t.pass(file)
174 }
175
176 #[test]
177 #[cfg(not(target_os = "windows"))] fn media_driver() {
179 if running_under_valgrind() {
180 return;
181 }
182
183 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
184 .join("bindings")
185 .join("media-driver.rs");
186 let mut bindings = parse_bindings(&path);
187 assert_eq!(
188 "AeronImageFragmentAssembler",
189 bindings
190 .wrappers
191 .get("aeron_image_fragment_assembler_t")
192 .unwrap()
193 .class_name
194 );
195
196 let file = write_to_file(TokenStream::new(), true, "md.rs");
197
198 let bindings_copy = bindings.clone();
199 for handler in bindings.handlers.iter_mut() {
200 let _ = crate::generate_handlers(handler, &bindings_copy);
202 }
203 for (p, w) in bindings
204 .wrappers
205 .values()
206 .filter(|w| !w.type_name.contains("pthread") && !w.type_name.contains("_t_"))
207 .enumerate()
208 {
209 let code = crate::generate_rust_code(w, &bindings.wrappers, p == 0, true, true, &bindings.handlers);
210 write_to_file(code, false, "md.rs");
211 }
212 let bindings_copy = bindings.clone();
213 for handler in bindings.handlers.iter_mut() {
214 let code = crate::generate_handlers(handler, &bindings_copy);
215 append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
216 }
217 let t = trybuild::TestCases::new();
218 append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
219 append_to_file(&file, MEDIA_DRIVER_BINDINGS).unwrap();
220 append_to_file(&file, "}").unwrap();
221 append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
222 append_to_file(&file, "\npub fn main() {}\n").unwrap();
223 t.pass(&file)
224 }
225
226 #[test]
227 #[cfg(not(target_os = "windows"))] fn archive() {
229 if running_under_valgrind() {
230 return;
231 }
232
233 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
234 .join("bindings")
235 .join("archive.rs");
236 let mut bindings = parse_bindings(&path);
237 assert_eq!(
238 "AeronImageFragmentAssembler",
239 bindings
240 .wrappers
241 .get("aeron_image_fragment_assembler_t")
242 .unwrap()
243 .class_name
244 );
245
246 let file = write_to_file(TokenStream::new(), true, "archive.rs");
247 let bindings_copy = bindings.clone();
248 for handler in bindings.handlers.iter_mut() {
249 let _ = crate::generate_handlers(handler, &bindings_copy);
251 }
252 for (p, w) in bindings.wrappers.values().enumerate() {
253 let code = crate::generate_rust_code(w, &bindings.wrappers, p == 0, true, true, &bindings.handlers);
254 write_to_file(code, false, "archive.rs");
255 }
256 let bindings_copy = bindings.clone();
257 for handler in bindings.handlers.iter_mut() {
258 let code = crate::generate_handlers(handler, &bindings_copy);
259 append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
260 }
261 let t = trybuild::TestCases::new();
262 append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
263 append_to_file(&file, ARCHIVE_BINDINGS).unwrap();
264 append_to_file(&file, "}").unwrap();
265 append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
266 append_to_file(&file, TRYBUILD_ARCHIVE_ERROR_STUB).unwrap();
273 append_to_file(&file, "\npub fn main() {}\n").unwrap();
274 t.pass(file)
275 }
276
277 fn assert_aeron_rs_snapshot_matches(
282 bindings_file: &str,
283 snapshot_rel_path: &str,
284 skip_filter: fn(&str) -> bool,
285 extra_custom_code: &[&str],
286 ) {
287 if running_under_valgrind() {
288 return;
289 }
290
291 let bindings_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
292 .join("bindings")
293 .join(bindings_file);
294 let snapshot_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(snapshot_rel_path);
295
296 let mut bindings = crate::parse_bindings_with_custom(&bindings_path, extra_custom_code);
297 let bindings_copy = bindings.clone();
299 for handler in bindings.handlers.iter_mut() {
300 let _ = crate::generate_handlers(handler, &bindings_copy);
301 }
302
303 let mut stream = TokenStream::new();
305 for (p, w) in bindings
306 .wrappers
307 .values()
308 .filter(|w| skip_filter(&w.type_name))
309 .enumerate()
310 {
311 let code = crate::generate_rust_code(
312 w,
313 &bindings.wrappers,
314 p == 0,
315 false, true,
317 &bindings.handlers,
318 );
319 stream.extend(code);
320 }
321 let bindings_copy = bindings.clone();
322 for handler in bindings.handlers.iter_mut() {
323 let code = crate::generate_handlers(handler, &bindings_copy);
324 stream.extend(code);
325 }
326
327 let generated = format_with_rustfmt(&stream.to_string())
328 .unwrap_or_else(|_| panic!("rustfmt failed on regenerated {bindings_file}"));
329 let committed = fs::read_to_string(&snapshot_path)
330 .unwrap_or_else(|_| panic!("missing committed snapshot: {}", snapshot_path.display()));
331
332 let norm = |s: &str| s.trim().to_string();
336 let (generated, committed) = (norm(&generated), norm(&committed));
337 if generated != committed {
338 let mut line_no = 0;
340 for (i, (g, c)) in generated.lines().zip(committed.lines()).enumerate() {
341 if g != c {
342 line_no = i + 1;
343 break;
344 }
345 }
346 if line_no == 0 {
347 line_no = generated.lines().count().min(committed.lines().count()) + 1;
348 }
349 panic!(
350 "docs-rs snapshot drift in {}: generated aeron.rs differs from committed \
351 {} (first divergence near line {}). GENERATED first 3 lines:\n{}\n\
352 COMMITTED first 3 lines:\n{}\nRebuild with `COPY_BINDINGS=true` \
353 (e.g. `just build`) and commit the regenerated snapshot.",
354 bindings_file,
355 snapshot_path.display(),
356 line_no,
357 generated.lines().take(3).collect::<Vec<_>>().join("\n"),
358 committed.lines().take(3).collect::<Vec<_>>().join("\n"),
359 );
360 }
361 }
362
363 #[test]
364 #[cfg(not(target_os = "windows"))]
365 fn client_aeron_rs_matches_committed_snapshot() {
366 assert_aeron_rs_snapshot_matches(
367 "client.rs",
368 "../rusteron-client/docs-rs/aeron.rs",
369 |t| t.starts_with("aeron_"),
370 &[],
371 );
372 }
373
374 #[test]
375 #[cfg(not(target_os = "windows"))]
376 fn archive_aeron_rs_matches_committed_snapshot() {
377 assert_aeron_rs_snapshot_matches(
386 "archive.rs",
387 "../rusteron-archive/docs-rs/aeron.rs",
388 |t| t.starts_with("aeron_"),
389 &[crate::CUSTOM_ARCHIVE_CODE],
390 );
391 }
392
393 #[test]
394 #[cfg(not(target_os = "windows"))]
395 fn media_driver_aeron_rs_matches_committed_snapshot() {
396 assert_aeron_rs_snapshot_matches(
398 "media-driver.rs",
399 "../rusteron-media-driver/docs-rs/aeron.rs",
400 |t| !t.contains("pthread") && !t.contains("_t_"),
401 &[],
402 );
403 }
404
405 #[test]
409 fn aeron_custom_rs_snapshots_match_source() {
410 let no_extra: &[&str] = &[];
413 for (crate_name, extra) in [
414 ("client", no_extra),
415 ("archive", &[crate::CUSTOM_ARCHIVE_CODE][..]),
416 ("media-driver", no_extra),
417 ] {
418 let mut source = format!("\n{}\n", CUSTOM_AERON_CODE);
420 for code in extra {
421 source.push_str(&format!("\n{}\n", code));
422 }
423 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
424 .join(format!("../rusteron-{crate_name}/docs-rs/aeron_custom.rs"));
425 let committed = fs::read_to_string(&path)
426 .unwrap_or_else(|_| panic!("missing committed aeron_custom.rs: {}", path.display()));
427 assert_eq!(
428 committed.trim(),
429 source.trim(),
430 "rusteron-{crate_name}/docs-rs/aeron_custom.rs drifted from \
431 rusteron-code-gen/src/aeron_custom*.rs. Rebuild with `COPY_BINDINGS=true`.",
432 );
433 }
434 }
435
436 fn write_to_file(rust_code: TokenStream, delete: bool, name: &str) -> String {
437 let src = format_token_stream(rust_code);
438 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!("../target/{}", name));
439 let path = path.to_str().unwrap();
440 if delete {
441 let _ = fs::remove_file(path);
442 }
443 append_to_file(path, &src).unwrap();
444 path.to_string()
445 }
446}
447
448#[cfg(test)]
449mod test {
450 use crate::{CResource, CleanupBox, ManagedCResource};
451
452 use crate::common::RcOrArc;
453 use std::sync::atomic::{AtomicBool, Ordering};
454 use std::sync::Arc;
455 #[allow(unused_imports)]
456 use RcOrArc as Rc;
457
458 fn make_resource(val: i32) -> *mut i32 {
459 Box::into_raw(Box::new(val))
460 }
461
462 unsafe fn reclaim_resource(ptr: *mut i32) {
463 if !ptr.is_null() {
464 let _ = Box::from_raw(ptr);
465 }
466 }
467
468 #[test]
469 fn test_drop_calls_cleanup_non_borrowed_no_cleanup_struct() {
470 let flag = Arc::new(AtomicBool::new(false));
471 let flag_clone = flag.clone();
472 let resource_ptr = make_resource(10);
473
474 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
475 flag_clone.store(true, Ordering::SeqCst);
476 unsafe {
478 reclaim_resource(*res);
479 *res = std::ptr::null_mut();
480 }
481 0
482 }) as CleanupBox<i32>);
483
484 {
485 let _resource = ManagedCResource::new(
486 |res: *mut *mut i32| {
487 unsafe {
488 *res = resource_ptr;
489 }
490 0
491 },
492 cleanup,
493 false,
494 );
495 assert!(_resource.is_ok())
496 }
497 assert!(flag.load(Ordering::SeqCst));
498 }
499
500 #[test]
501 fn test_drop_calls_cleanup_non_borrowed_with_cleanup_struct() {
502 let flag = Arc::new(AtomicBool::new(false));
503 let flag_clone = flag.clone();
504 let resource_ptr = make_resource(20);
505
506 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
507 flag_clone.store(true, Ordering::SeqCst);
508 unsafe {
509 *res = std::ptr::null_mut();
510 }
511 0
512 }) as CleanupBox<i32>);
513
514 {
515 let _resource = ManagedCResource::new(
516 |res: *mut *mut i32| {
517 unsafe {
518 *res = resource_ptr;
519 }
520 0
521 },
522 cleanup,
523 true,
524 );
525 assert!(_resource.is_ok())
526 }
527 assert!(flag.load(Ordering::SeqCst));
528 }
529
530 #[test]
531 fn test_drop_does_not_call_cleanup_if_already_closed() {
532 let flag = Arc::new(AtomicBool::new(false));
533 let flag_clone = flag.clone();
534 let resource_ptr = make_resource(30);
535
536 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
537 flag_clone.store(true, Ordering::SeqCst);
538 unsafe {
539 reclaim_resource(*res);
540 *res = std::ptr::null_mut();
541 }
542 0
543 }) as CleanupBox<i32>);
544
545 let resource = ManagedCResource::new(
546 |res: *mut *mut i32| {
547 unsafe {
548 *res = resource_ptr;
549 }
550 0
551 },
552 cleanup,
553 false,
554 );
555 assert!(resource.is_ok());
556
557 if let Ok(ref resource) = resource {
558 assert!(resource.close_shared().is_ok());
559 }
560
561 flag.store(false, Ordering::SeqCst);
563 drop(resource);
564 assert!(!flag.load(Ordering::SeqCst));
565 }
566
567 #[test]
568 fn close_resource_closes_shared_resource_once_across_clones() {
569 let close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
570 let close_count_for_cleanup = close_count.clone();
571 let resource_ptr = make_resource(40);
572
573 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
574 close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
575 unsafe {
576 reclaim_resource(*res);
577 *res = std::ptr::null_mut();
578 }
579 0
580 }) as CleanupBox<i32>);
581
582 let resource = ManagedCResource::new(
583 |res: *mut *mut i32| {
584 unsafe {
585 *res = resource_ptr;
586 }
587 0
588 },
589 cleanup,
590 false,
591 );
592 assert!(resource.is_ok());
593 let resource = resource.ok().unwrap();
594 let inner = Rc::new(resource);
595 let handle_one = CResource::OwnedOnHeap(inner.clone());
596 let handle_two = CResource::OwnedOnHeap(inner);
597
598 assert!(!handle_one.get().is_null());
599 assert!(!handle_two.get().is_null());
600
601 assert!(handle_one.close_resource().is_ok());
602 assert_eq!(1, close_count.load(Ordering::SeqCst));
603 assert!(handle_one.get().is_null());
604 assert!(handle_two.get().is_null());
605
606 assert!(handle_two.close_resource().is_ok());
607 assert_eq!(1, close_count.load(Ordering::SeqCst));
608 }
609
610 #[test]
611 fn close_resource_defers_owner_close_while_dependent_is_alive() {
612 let close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
613 let close_count_for_cleanup = close_count.clone();
614 let owner_ptr = make_resource(60);
615 let child_ptr = make_resource(61);
616
617 let owner_cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
618 close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
619 unsafe {
620 reclaim_resource(*res);
621 *res = std::ptr::null_mut();
622 }
623 0
624 }) as CleanupBox<i32>);
625
626 let owner = ManagedCResource::new(
627 |res: *mut *mut i32| {
628 unsafe {
629 *res = owner_ptr;
630 }
631 0
632 },
633 owner_cleanup,
634 false,
635 );
636 assert!(owner.is_ok());
637 let owner = CResource::OwnedOnHeap(Rc::new(owner.ok().unwrap()));
638
639 let child = ManagedCResource::new(
640 |res: *mut *mut i32| {
641 unsafe {
642 *res = child_ptr;
643 }
644 0
645 },
646 Some(Box::new(move |res| {
647 unsafe {
648 reclaim_resource(*res);
649 *res = std::ptr::null_mut();
650 }
651 0
652 })),
653 false,
654 );
655 assert!(child.is_ok());
656 let child = CResource::OwnedOnHeap(Rc::new(child.ok().unwrap()));
657 child.add_dependency(owner.clone());
658
659 assert!(owner.close_resource_deferred_if_shared().is_ok());
660 assert_eq!(0, close_count.load(Ordering::SeqCst));
661 assert!(!owner.get().is_null());
662
663 drop(owner);
664 drop(child);
665 assert_eq!(1, close_count.load(Ordering::SeqCst));
666 }
667
668 #[test]
669 fn close_resource_deferral_is_retryable_if_owner_close_fails_after_dependent_drops() {
670 let close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
671 let close_count_for_cleanup = close_count.clone();
672 let owner_ptr = make_resource(70);
673 let child_ptr = make_resource(71);
674
675 let owner_cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
676 let count = close_count_for_cleanup.fetch_add(1, Ordering::SeqCst) + 1;
677 if count == 1 {
678 return -1;
679 }
680 unsafe {
681 reclaim_resource(*res);
682 *res = std::ptr::null_mut();
683 }
684 0
685 }) as CleanupBox<i32>);
686
687 let owner = ManagedCResource::new(
688 |res: *mut *mut i32| {
689 unsafe {
690 *res = owner_ptr;
691 }
692 0
693 },
694 owner_cleanup,
695 false,
696 );
697 assert!(owner.is_ok());
698 let owner = CResource::OwnedOnHeap(Rc::new(owner.ok().unwrap()));
699 let owner_retry_handle = owner.clone();
700
701 let child = ManagedCResource::new(
702 |res: *mut *mut i32| {
703 unsafe {
704 *res = child_ptr;
705 }
706 0
707 },
708 Some(Box::new(move |res| {
709 unsafe {
710 reclaim_resource(*res);
711 *res = std::ptr::null_mut();
712 }
713 0
714 })),
715 false,
716 );
717 assert!(child.is_ok());
718 let child = CResource::OwnedOnHeap(Rc::new(child.ok().unwrap()));
719 child.add_dependency(owner.clone());
720
721 assert!(owner.close_resource_deferred_if_shared().is_ok());
722 drop(owner);
723 drop(child);
724 assert_eq!(0, close_count.load(Ordering::SeqCst));
725 assert!(!owner_retry_handle.get().is_null());
726
727 assert!(owner_retry_handle.close_resource_deferred_if_shared().is_err());
728 assert_eq!(1, close_count.load(Ordering::SeqCst));
729 assert!(!owner_retry_handle.get().is_null());
730
731 assert!(owner_retry_handle.close_resource_deferred_if_shared().is_ok());
732 assert_eq!(2, close_count.load(Ordering::SeqCst));
733 assert!(owner_retry_handle.get().is_null());
734 }
735
736 #[test]
737 fn close_resource_propagates_failure_and_allows_retry() {
738 let close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
739 let close_count_for_cleanup = close_count.clone();
740 let resource_ptr = make_resource(50);
741
742 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
743 let count = close_count_for_cleanup.fetch_add(1, Ordering::SeqCst) + 1;
744 if count == 1 {
745 return -1;
746 }
747 unsafe {
748 reclaim_resource(*res);
749 *res = std::ptr::null_mut();
750 }
751 0
752 }) as CleanupBox<i32>);
753
754 let resource = ManagedCResource::new(
755 |res: *mut *mut i32| {
756 unsafe {
757 *res = resource_ptr;
758 }
759 0
760 },
761 cleanup,
762 false,
763 );
764 assert!(resource.is_ok());
765 let resource = resource.ok().unwrap();
766 let handle = CResource::OwnedOnHeap(Rc::new(resource));
767
768 assert!(handle.close_resource().is_err());
769 assert_eq!(1, close_count.load(Ordering::SeqCst));
770 assert!(!handle.get().is_null());
771
772 assert!(handle.close_resource().is_ok());
773 assert_eq!(2, close_count.load(Ordering::SeqCst));
774 assert!(handle.get().is_null());
775 }
776
777 #[test]
778 fn close_resource_with_uses_custom_cleanup_once_across_clones() {
779 let default_close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
780 let custom_close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
781 let default_close_count_for_cleanup = default_close_count.clone();
782 let resource_ptr = make_resource(80);
783
784 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
785 default_close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
786 unsafe {
787 reclaim_resource(*res);
788 *res = std::ptr::null_mut();
789 }
790 0
791 }) as CleanupBox<i32>);
792
793 let resource = ManagedCResource::new(
794 |res: *mut *mut i32| {
795 unsafe {
796 *res = resource_ptr;
797 }
798 0
799 },
800 cleanup,
801 false,
802 );
803 assert!(resource.is_ok());
804 let inner = Rc::new(resource.ok().unwrap());
805 let handle_one = CResource::OwnedOnHeap(inner.clone());
806 let handle_two = CResource::OwnedOnHeap(inner);
807
808 let custom_close_count_for_cleanup = custom_close_count.clone();
809 assert!(handle_one
810 .close_resource_with(move |res| {
811 custom_close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
812 unsafe {
813 reclaim_resource(*res);
814 *res = std::ptr::null_mut();
815 }
816 0
817 })
818 .is_ok());
819
820 assert_eq!(0, default_close_count.load(Ordering::SeqCst));
821 assert_eq!(1, custom_close_count.load(Ordering::SeqCst));
822 assert!(handle_one.get().is_null());
823 assert!(handle_two.get().is_null());
824
825 assert!(handle_two.close_resource().is_ok());
826 assert_eq!(0, default_close_count.load(Ordering::SeqCst));
827 assert_eq!(1, custom_close_count.load(Ordering::SeqCst));
828 }
829
830 #[test]
831 fn close_resource_with_failure_restores_default_cleanup_for_retry() {
832 let default_close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
833 let custom_close_count = RcOrArc::new(std::sync::atomic::AtomicI32::new(0));
834 let default_close_count_for_cleanup = default_close_count.clone();
835 let resource_ptr = make_resource(90);
836
837 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
838 default_close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
839 unsafe {
840 reclaim_resource(*res);
841 *res = std::ptr::null_mut();
842 }
843 0
844 }) as CleanupBox<i32>);
845
846 let resource = ManagedCResource::new(
847 |res: *mut *mut i32| {
848 unsafe {
849 *res = resource_ptr;
850 }
851 0
852 },
853 cleanup,
854 false,
855 );
856 assert!(resource.is_ok());
857 let handle = CResource::OwnedOnHeap(Rc::new(resource.ok().unwrap()));
858
859 let custom_close_count_for_cleanup = custom_close_count.clone();
860 assert!(handle
861 .close_resource_with(move |_res| {
862 custom_close_count_for_cleanup.fetch_add(1, Ordering::SeqCst);
863 -1
864 })
865 .is_err());
866
867 assert_eq!(0, default_close_count.load(Ordering::SeqCst));
868 assert_eq!(1, custom_close_count.load(Ordering::SeqCst));
869 assert!(!handle.get().is_null());
870
871 assert!(handle.close_resource().is_ok());
872 assert_eq!(1, default_close_count.load(Ordering::SeqCst));
873 assert_eq!(1, custom_close_count.load(Ordering::SeqCst));
874 assert!(handle.get().is_null());
875 }
876
877 #[test]
878 fn close_resource_is_noop_for_stack_and_borrowed_resources() {
879 let mut borrowed_value = 10;
880 let borrowed = CResource::Borrowed(&mut borrowed_value as *mut i32);
881 assert!(borrowed.close_resource().is_ok());
882 assert_eq!(10, borrowed_value);
883
884 let stack = CResource::OwnedOnStack(std::mem::MaybeUninit::new(20));
885 assert!(stack.close_resource().is_ok());
886 assert_eq!(20, unsafe { *stack.get() });
887 }
888
889 }