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 common;
11mod generator;
12mod parser;
13pub mod test_logger;
14
15pub use common::*;
16pub use generator::*;
17pub use parser::*;
18
19use proc_macro2::TokenStream;
20use std::fs::OpenOptions;
21use std::io::Write;
22use std::path::Path;
23use std::process::{Command, Stdio};
24
25pub const CUSTOM_AERON_CODE: &str = include_str!("./aeron_custom.rs");
26pub const COMMON_CODE: &str = include_str!("./common.rs");
27
28pub fn append_to_file(file_path: &str, code: &str) -> std::io::Result<()> {
29 let path = Path::new(file_path);
30 if let Some(parent) = path.parent() {
31 if !parent.as_os_str().is_empty() {
32 std::fs::create_dir_all(parent)?;
33 }
34 }
35
36 let mut file = OpenOptions::new().create(true).write(true).append(true).open(path)?;
37
38 writeln!(file, "\n{}", code)?;
39
40 Ok(())
41}
42
43#[allow(dead_code)]
44pub fn format_with_rustfmt(code: &str) -> Result<String, std::io::Error> {
45 let mut rustfmt = Command::new("rustfmt")
46 .stdin(Stdio::piped())
47 .stdout(Stdio::piped())
48 .spawn()?;
49
50 if let Some(mut stdin) = rustfmt.stdin.take() {
51 stdin.write_all(code.as_bytes())?;
52 }
53
54 let output = rustfmt.wait_with_output()?;
55
56 if !output.status.success() {
57 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
58 return Err(std::io::Error::new(
59 std::io::ErrorKind::Other,
60 format!("rustfmt failed: {}", stderr),
61 ));
62 }
63
64 let formatted_code = String::from_utf8_lossy(&output.stdout).to_string();
65
66 if !code.trim().is_empty() && formatted_code.trim().is_empty() {
69 Err(std::io::Error::new(
70 std::io::ErrorKind::Other,
71 "rustfmt produced empty output",
72 ))
73 } else {
74 Ok(formatted_code)
75 }
76}
77
78#[allow(dead_code)]
79pub fn format_token_stream(tokens: TokenStream) -> String {
80 let code = tokens.to_string();
81
82 match format_with_rustfmt(&code) {
83 Ok(formatted_code) if !formatted_code.trim().is_empty() => formatted_code,
84 _ => code.replace("{", "{\n"), }
86}
87
88#[cfg(test)]
89mod tests {
90 use crate::generator::MEDIA_DRIVER_BINDINGS;
91 use crate::parser::parse_bindings;
92 use crate::{
93 append_to_file, format_token_stream, format_with_rustfmt, ARCHIVE_BINDINGS, CLIENT_BINDINGS, CUSTOM_AERON_CODE,
94 };
95 use proc_macro2::TokenStream;
96 use std::fs;
97
98 fn running_under_valgrind() -> bool {
101 std::env::var_os("RUSTERON_VALGRIND").is_some()
102 }
103
104 #[test]
105 #[cfg(not(target_os = "windows"))] fn client() {
107 if running_under_valgrind() {
108 return;
109 }
110
111 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
112 .join("bindings")
113 .join("client.rs");
114 let mut bindings = parse_bindings(&path);
115 assert_eq!(
116 "AeronImageFragmentAssembler",
117 bindings
118 .wrappers
119 .get("aeron_image_fragment_assembler_t")
120 .unwrap()
121 .class_name
122 );
123 assert_eq!(
124 0,
125 bindings.methods.len(),
126 "expected all methods to have been matched {:#?}",
127 bindings.methods
128 );
129
130 let file = write_to_file(TokenStream::new(), true, "client.rs");
131 let bindings_copy = bindings.clone();
132 for handler in bindings.handlers.iter_mut() {
133 let _ = crate::generate_handlers(handler, &bindings_copy);
135 }
136 for (p, w) in bindings.wrappers.values().enumerate() {
137 let code = crate::generate_rust_code(w, &bindings.wrappers, p == 0, true, true, &bindings.handlers);
138 write_to_file(code, false, "client.rs");
139 }
140 let bindings_copy = bindings.clone();
141 for handler in bindings.handlers.iter_mut() {
142 let code = crate::generate_handlers(handler, &bindings_copy);
143 append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
144 }
145
146 let t = trybuild::TestCases::new();
147 append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
148 append_to_file(&file, CLIENT_BINDINGS).unwrap();
149 append_to_file(&file, "}").unwrap();
150 append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
151 append_to_file(&file, "\npub fn main() {}\n").unwrap();
152 t.pass(file)
153 }
154
155 #[test]
156 #[cfg(not(target_os = "windows"))] fn media_driver() {
158 if running_under_valgrind() {
159 return;
160 }
161
162 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
163 .join("bindings")
164 .join("media-driver.rs");
165 let mut bindings = parse_bindings(&path);
166 assert_eq!(
167 "AeronImageFragmentAssembler",
168 bindings
169 .wrappers
170 .get("aeron_image_fragment_assembler_t")
171 .unwrap()
172 .class_name
173 );
174
175 let file = write_to_file(TokenStream::new(), true, "md.rs");
176
177 let bindings_copy = bindings.clone();
178 for handler in bindings.handlers.iter_mut() {
179 let _ = crate::generate_handlers(handler, &bindings_copy);
181 }
182 for (p, w) in bindings
183 .wrappers
184 .values()
185 .filter(|w| !w.type_name.contains("_t_") && w.type_name != "in_addr")
186 .enumerate()
187 {
188 let code = crate::generate_rust_code(w, &bindings.wrappers, p == 0, true, true, &bindings.handlers);
189 write_to_file(code, false, "md.rs");
190 }
191 let bindings_copy = bindings.clone();
192 for handler in bindings.handlers.iter_mut() {
193 let code = crate::generate_handlers(handler, &bindings_copy);
194 append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
195 }
196 let t = trybuild::TestCases::new();
197 append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
198 append_to_file(&file, MEDIA_DRIVER_BINDINGS).unwrap();
199 append_to_file(&file, "}").unwrap();
200 append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
201 append_to_file(&file, "\npub fn main() {}\n").unwrap();
202 t.pass(&file)
203 }
204
205 #[test]
206 #[cfg(not(target_os = "windows"))] fn archive() {
208 if running_under_valgrind() {
209 return;
210 }
211
212 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
213 .join("bindings")
214 .join("archive.rs");
215 let mut bindings = parse_bindings(&path);
216 assert_eq!(
217 "AeronImageFragmentAssembler",
218 bindings
219 .wrappers
220 .get("aeron_image_fragment_assembler_t")
221 .unwrap()
222 .class_name
223 );
224
225 let file = write_to_file(TokenStream::new(), true, "archive.rs");
226 let bindings_copy = bindings.clone();
227 for handler in bindings.handlers.iter_mut() {
228 let _ = crate::generate_handlers(handler, &bindings_copy);
230 }
231 for (p, w) in bindings.wrappers.values().enumerate() {
232 let code = crate::generate_rust_code(w, &bindings.wrappers, p == 0, true, true, &bindings.handlers);
233 write_to_file(code, false, "archive.rs");
234 }
235 let bindings_copy = bindings.clone();
236 for handler in bindings.handlers.iter_mut() {
237 let code = crate::generate_handlers(handler, &bindings_copy);
238 append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
239 }
240 let t = trybuild::TestCases::new();
241 append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
242 append_to_file(&file, ARCHIVE_BINDINGS).unwrap();
243 append_to_file(&file, "}").unwrap();
244 append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
245 append_to_file(&file, "\npub fn main() {}\n").unwrap();
246 t.pass(file)
247 }
248
249 fn assert_aeron_rs_snapshot_matches(bindings_file: &str, snapshot_rel_path: &str, skip_filter: fn(&str) -> bool) {
254 if running_under_valgrind() {
255 return;
256 }
257
258 let bindings_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
259 .join("bindings")
260 .join(bindings_file);
261 let snapshot_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(snapshot_rel_path);
262
263 let mut bindings = parse_bindings(&bindings_path);
264 let bindings_copy = bindings.clone();
266 for handler in bindings.handlers.iter_mut() {
267 let _ = crate::generate_handlers(handler, &bindings_copy);
268 }
269
270 let mut stream = TokenStream::new();
272 for (p, w) in bindings
273 .wrappers
274 .values()
275 .filter(|w| skip_filter(&w.type_name))
276 .enumerate()
277 {
278 let code = crate::generate_rust_code(
279 w,
280 &bindings.wrappers,
281 p == 0,
282 false, true,
284 &bindings.handlers,
285 );
286 stream.extend(code);
287 }
288 let bindings_copy = bindings.clone();
289 for handler in bindings.handlers.iter_mut() {
290 let code = crate::generate_handlers(handler, &bindings_copy);
291 stream.extend(code);
292 }
293
294 let generated = format_with_rustfmt(&stream.to_string())
295 .unwrap_or_else(|_| panic!("rustfmt failed on regenerated {bindings_file}"));
296 let committed = fs::read_to_string(&snapshot_path)
297 .unwrap_or_else(|_| panic!("missing committed snapshot: {}", snapshot_path.display()));
298
299 let norm = |s: &str| s.trim().to_string();
303 let (generated, committed) = (norm(&generated), norm(&committed));
304 if generated != committed {
305 let mut line_no = 0;
307 for (i, (g, c)) in generated.lines().zip(committed.lines()).enumerate() {
308 if g != c {
309 line_no = i + 1;
310 break;
311 }
312 }
313 if line_no == 0 {
314 line_no = generated.lines().count().min(committed.lines().count()) + 1;
315 }
316 panic!(
317 "docs-rs snapshot drift in {}: generated aeron.rs differs from committed \
318 {} (first divergence near line {}). GENERATED first 3 lines:\n{}\n\
319 COMMITTED first 3 lines:\n{}\nRebuild with `COPY_BINDINGS=true` \
320 (e.g. `just build`) and commit the regenerated snapshot.",
321 bindings_file,
322 snapshot_path.display(),
323 line_no,
324 generated.lines().take(3).collect::<Vec<_>>().join("\n"),
325 committed.lines().take(3).collect::<Vec<_>>().join("\n"),
326 );
327 }
328 }
329
330 #[test]
331 #[cfg(not(target_os = "windows"))]
332 fn client_aeron_rs_matches_committed_snapshot() {
333 assert_aeron_rs_snapshot_matches("client.rs", "../rusteron-client/docs-rs/aeron.rs", |_| true);
334 }
335
336 #[test]
337 #[cfg(not(target_os = "windows"))]
338 #[cfg(target_os = "macos")]
339 fn archive_aeron_rs_matches_committed_snapshot() {
340 assert_aeron_rs_snapshot_matches("archive.rs", "../rusteron-archive/docs-rs/aeron.rs", |_| true);
341 }
342
343 #[test]
344 #[cfg(not(target_os = "windows"))]
345 #[cfg(target_os = "macos")]
346 fn media_driver_aeron_rs_matches_committed_snapshot() {
347 assert_aeron_rs_snapshot_matches("media-driver.rs", "../rusteron-media-driver/docs-rs/aeron.rs", |t| {
349 !t.contains("_t_") && t != "in_addr"
350 });
351 }
352
353 #[test]
357 fn aeron_custom_rs_snapshots_match_source() {
358 let source = CUSTOM_AERON_CODE.trim();
359 for crate_name in ["client", "archive", "media-driver"] {
360 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
361 .join(format!("../rusteron-{crate_name}/docs-rs/aeron_custom.rs"));
362 let committed = fs::read_to_string(&path)
363 .unwrap_or_else(|_| panic!("missing committed aeron_custom.rs: {}", path.display()));
364 assert_eq!(
365 committed.trim(),
366 source,
367 "rusteron-{crate_name}/docs-rs/aeron_custom.rs drifted from \
368 rusteron-code-gen/src/aeron_custom.rs. Rebuild with `COPY_BINDINGS=true`.",
369 );
370 }
371 }
372
373 fn write_to_file(rust_code: TokenStream, delete: bool, name: &str) -> String {
374 let src = format_token_stream(rust_code);
375 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!("../target/{}", name));
376 let path = path.to_str().unwrap();
377 if delete {
378 let _ = fs::remove_file(path);
379 }
380 append_to_file(path, &src).unwrap();
381 path.to_string()
382 }
383}
384
385#[cfg(test)]
386mod test {
387 use crate::ManagedCResource;
388
389 use std::sync::atomic::{AtomicBool, Ordering};
390 use std::sync::Arc;
391
392 fn make_resource(val: i32) -> *mut i32 {
393 Box::into_raw(Box::new(val))
394 }
395
396 unsafe fn reclaim_resource(ptr: *mut i32) {
397 if !ptr.is_null() {
398 let _ = Box::from_raw(ptr);
399 }
400 }
401
402 #[test]
403 fn test_drop_calls_cleanup_non_borrowed_no_cleanup_struct() {
404 let flag = Arc::new(AtomicBool::new(false));
405 let flag_clone = flag.clone();
406 let resource_ptr = make_resource(10);
407
408 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
409 flag_clone.store(true, Ordering::SeqCst);
410 unsafe {
412 reclaim_resource(*res);
413 *res = std::ptr::null_mut();
414 }
415 0
416 }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
417
418 {
419 let _resource = ManagedCResource::new(
420 |res: *mut *mut i32| {
421 unsafe {
422 *res = resource_ptr;
423 }
424 0
425 },
426 cleanup,
427 false,
428 None,
429 );
430 assert!(_resource.is_ok())
431 }
432 assert!(flag.load(Ordering::SeqCst));
433 }
434
435 #[test]
436 fn test_drop_calls_cleanup_non_borrowed_with_cleanup_struct() {
437 let flag = Arc::new(AtomicBool::new(false));
438 let flag_clone = flag.clone();
439 let resource_ptr = make_resource(20);
440
441 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
442 flag_clone.store(true, Ordering::SeqCst);
443 unsafe {
444 *res = std::ptr::null_mut();
445 }
446 0
447 }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
448
449 {
450 let _resource = ManagedCResource::new(
451 |res: *mut *mut i32| {
452 unsafe {
453 *res = resource_ptr;
454 }
455 0
456 },
457 cleanup,
458 true,
459 None,
460 );
461 assert!(_resource.is_ok())
462 }
463 assert!(flag.load(Ordering::SeqCst));
464 }
465
466 #[test]
467 fn test_drop_does_not_call_cleanup_if_already_closed() {
468 let flag = Arc::new(AtomicBool::new(false));
469 let flag_clone = flag.clone();
470 let resource_ptr = make_resource(30);
471
472 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
473 flag_clone.store(true, Ordering::SeqCst);
474 unsafe {
475 reclaim_resource(*res);
476 *res = std::ptr::null_mut();
477 }
478 0
479 }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
480
481 let mut resource = ManagedCResource::new(
482 |res: *mut *mut i32| {
483 unsafe {
484 *res = resource_ptr;
485 }
486 0
487 },
488 cleanup,
489 false,
490 None,
491 );
492 assert!(resource.is_ok());
493
494 if let Ok(ref mut resource) = &mut resource {
495 assert!(resource.close().is_ok())
496 }
497
498 flag.store(false, Ordering::SeqCst);
500 drop(resource);
501 assert!(!flag.load(Ordering::SeqCst));
502 }
503
504 #[test]
505 fn test_drop_does_not_call_cleanup_if_check_for_is_closed_returns_true() {
506 let flag = Arc::new(AtomicBool::new(false));
507 let flag_clone = flag.clone();
508 let resource_ptr = make_resource(60);
509
510 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
511 flag_clone.store(true, Ordering::SeqCst);
512 unsafe {
513 reclaim_resource(*res);
514 *res = std::ptr::null_mut();
515 }
516 0
517 }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
518
519 let check_fn = Some(|_res: *mut i32| -> bool { true } as fn(_) -> bool);
520
521 {
522 let _resource = ManagedCResource::new(
523 |res: *mut *mut i32| {
524 unsafe {
525 *res = resource_ptr;
526 }
527 0
528 },
529 cleanup,
530 false,
531 check_fn,
532 );
533 assert!(_resource.is_ok());
534 }
535 assert!(!flag.load(Ordering::SeqCst));
536 unsafe {
537 reclaim_resource(resource_ptr);
538 }
539 }
540
541 #[test]
542 fn test_drop_does_call_cleanup_if_check_for_is_closed_returns_false() {
543 let flag = Arc::new(AtomicBool::new(false));
544 let flag_clone = flag.clone();
545 let resource_ptr = make_resource(60);
546
547 let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
548 flag_clone.store(true, Ordering::SeqCst);
549 unsafe {
550 reclaim_resource(*res);
551 *res = std::ptr::null_mut();
552 }
553 0
554 }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
555
556 let check_fn = Some(|_res: *mut i32| -> bool { false } as fn(*mut i32) -> bool);
557
558 {
559 let _resource = ManagedCResource::new(
560 |res: *mut *mut i32| {
561 unsafe {
562 *res = resource_ptr;
563 }
564 0
565 },
566 cleanup,
567 false,
568 check_fn,
569 );
570 assert!(_resource.is_ok())
571 }
572 assert!(flag.load(Ordering::SeqCst));
573 }
574}