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