rusteron_code_gen/
lib.rs

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;
13
14pub use common::*;
15pub use generator::*;
16pub use parser::*;
17
18use proc_macro2::TokenStream;
19use std::fs::OpenOptions;
20use std::io::Write;
21use std::process::{Command, Stdio};
22
23pub const CUSTOM_AERON_CODE: &str = include_str!("./aeron_custom.rs");
24pub const COMMON_CODE: &str = include_str!("./common.rs");
25
26pub fn append_to_file(file_path: &str, code: &str) -> std::io::Result<()> {
27    // Open the file in append mode
28    let mut file = OpenOptions::new()
29        .create(true)
30        .write(true)
31        .append(true)
32        .open(file_path)?;
33
34    // Write the generated code to the file
35    writeln!(file, "\n{}", code)?;
36
37    Ok(())
38}
39
40#[allow(dead_code)]
41pub fn format_with_rustfmt(code: &str) -> Result<String, std::io::Error> {
42    let mut rustfmt = Command::new("rustfmt")
43        .stdin(Stdio::piped())
44        .stdout(Stdio::piped())
45        .spawn()?;
46
47    if let Some(mut stdin) = rustfmt.stdin.take() {
48        stdin.write_all(code.as_bytes())?;
49    }
50
51    let output = rustfmt.wait_with_output()?;
52    let formatted_code = String::from_utf8_lossy(&output.stdout).to_string();
53
54    Ok(formatted_code)
55}
56
57#[allow(dead_code)]
58pub fn format_token_stream(tokens: TokenStream) -> String {
59    let code = tokens.to_string();
60
61    match format_with_rustfmt(&code) {
62        Ok(formatted_code) if !formatted_code.trim().is_empty() => formatted_code,
63        _ => code.replace("{", "{\n"), // Fallback to unformatted code in case of error
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use crate::generator::MEDIA_DRIVER_BINDINGS;
70    use crate::parser::parse_bindings;
71    use crate::{
72        append_to_file, format_token_stream, format_with_rustfmt, ARCHIVE_BINDINGS,
73        CLIENT_BINDINGS, CUSTOM_AERON_CODE,
74    };
75    use proc_macro2::TokenStream;
76    use std::fs;
77
78    #[test]
79    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
80    fn client() {
81        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/client.rs".into());
82        assert_eq!(
83            "AeronImageFragmentAssembler",
84            bindings
85                .wrappers
86                .get("aeron_image_fragment_assembler_t")
87                .unwrap()
88                .class_name
89        );
90        assert_eq!(
91            0,
92            bindings.methods.len(),
93            "expected all methods to have been matched {:#?}",
94            bindings.methods
95        );
96
97        let file = write_to_file(TokenStream::new(), true, "client.rs");
98        let bindings_copy = bindings.clone();
99        for handler in bindings.handlers.iter_mut() {
100            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
101            let _ = crate::generate_handlers(handler, &bindings_copy);
102        }
103        for (p, w) in bindings.wrappers.values().enumerate() {
104            let code = crate::generate_rust_code(
105                w,
106                &bindings.wrappers,
107                p == 0,
108                true,
109                true,
110                &bindings.handlers,
111            );
112            write_to_file(code, false, "client.rs");
113        }
114        let bindings_copy = bindings.clone();
115        for handler in bindings.handlers.iter_mut() {
116            let code = crate::generate_handlers(handler, &bindings_copy);
117            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
118        }
119
120        let t = trybuild::TestCases::new();
121        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
122        append_to_file(&file, CLIENT_BINDINGS).unwrap();
123        append_to_file(&file, "}").unwrap();
124        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
125        append_to_file(&file, "\npub fn main() {}\n").unwrap();
126        t.pass(file)
127    }
128
129    #[test]
130    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
131    fn media_driver() {
132        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/media-driver.rs".into());
133        assert_eq!(
134            "AeronImageFragmentAssembler",
135            bindings
136                .wrappers
137                .get("aeron_image_fragment_assembler_t")
138                .unwrap()
139                .class_name
140        );
141
142        let file = write_to_file(TokenStream::new(), true, "md.rs");
143
144        let bindings_copy = bindings.clone();
145        for handler in bindings.handlers.iter_mut() {
146            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
147            let _ = crate::generate_handlers(handler, &bindings_copy);
148        }
149        for (p, w) in bindings
150            .wrappers
151            .values()
152            .filter(|w| !w.type_name.contains("_t_") && w.type_name != "in_addr")
153            .enumerate()
154        {
155            let code = crate::generate_rust_code(
156                w,
157                &bindings.wrappers,
158                p == 0,
159                true,
160                true,
161                &bindings.handlers,
162            );
163            write_to_file(code, false, "md.rs");
164        }
165        let bindings_copy = bindings.clone();
166        for handler in bindings.handlers.iter_mut() {
167            let code = crate::generate_handlers(handler, &bindings_copy);
168            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
169        }
170        let t = trybuild::TestCases::new();
171        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
172        append_to_file(&file, MEDIA_DRIVER_BINDINGS).unwrap();
173        append_to_file(&file, "}").unwrap();
174        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
175        append_to_file(&file, "\npub fn main() {}\n").unwrap();
176        t.pass(&file)
177    }
178
179    #[test]
180    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
181    fn archive() {
182        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/archive.rs".into());
183        assert_eq!(
184            "AeronImageFragmentAssembler",
185            bindings
186                .wrappers
187                .get("aeron_image_fragment_assembler_t")
188                .unwrap()
189                .class_name
190        );
191
192        let file = write_to_file(TokenStream::new(), true, "archive.rs");
193        let bindings_copy = bindings.clone();
194        for handler in bindings.handlers.iter_mut() {
195            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
196            let _ = crate::generate_handlers(handler, &bindings_copy);
197        }
198        for (p, w) in bindings.wrappers.values().enumerate() {
199            let code = crate::generate_rust_code(
200                w,
201                &bindings.wrappers,
202                p == 0,
203                true,
204                true,
205                &bindings.handlers,
206            );
207            write_to_file(code, false, "archive.rs");
208        }
209        let bindings_copy = bindings.clone();
210        for handler in bindings.handlers.iter_mut() {
211            let code = crate::generate_handlers(handler, &bindings_copy);
212            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
213        }
214        let t = trybuild::TestCases::new();
215        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
216        append_to_file(&file, ARCHIVE_BINDINGS).unwrap();
217        append_to_file(&file, "}").unwrap();
218        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
219        append_to_file(&file, "\npub fn main() {}\n").unwrap();
220        t.pass(file)
221    }
222
223    fn write_to_file(rust_code: TokenStream, delete: bool, name: &str) -> String {
224        let src = format_token_stream(rust_code);
225        let path = format!("../target/{name}");
226        let path = &path;
227        if delete {
228            let _ = fs::remove_file(path);
229        }
230        append_to_file(path, &src).unwrap();
231        path.to_string()
232    }
233}
234
235#[cfg(test)]
236mod test {
237    use crate::ManagedCResource;
238
239    use std::sync::atomic::{AtomicBool, Ordering};
240    use std::sync::Arc;
241
242    fn make_resource(val: i32) -> *mut i32 {
243        Box::into_raw(Box::new(val))
244    }
245
246    #[test]
247    fn test_drop_calls_cleanup_non_borrowed_no_cleanup_struct() {
248        let flag = Arc::new(AtomicBool::new(false));
249        let flag_clone = flag.clone();
250        let resource_ptr = make_resource(10);
251
252        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
253            flag_clone.store(true, Ordering::SeqCst);
254            // Set the resource to null to simulate cleanup.
255            unsafe {
256                *res = std::ptr::null_mut();
257            }
258            0
259        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
260
261        {
262            let _resource = ManagedCResource::new(
263                |res: *mut *mut i32| {
264                    unsafe {
265                        *res = resource_ptr;
266                    }
267                    0
268                },
269                cleanup,
270                false,
271                None,
272            );
273            assert!(_resource.is_ok())
274        }
275        assert!(flag.load(Ordering::SeqCst));
276    }
277
278    #[test]
279    fn test_drop_calls_cleanup_non_borrowed_with_cleanup_struct() {
280        let flag = Arc::new(AtomicBool::new(false));
281        let flag_clone = flag.clone();
282        let resource_ptr = make_resource(20);
283
284        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
285            flag_clone.store(true, Ordering::SeqCst);
286            unsafe {
287                *res = std::ptr::null_mut();
288            }
289            0
290        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
291
292        {
293            let _resource = ManagedCResource::new(
294                |res: *mut *mut i32| {
295                    unsafe {
296                        *res = resource_ptr;
297                    }
298                    0
299                },
300                cleanup,
301                true,
302                None,
303            );
304            assert!(_resource.is_ok())
305        }
306        assert!(flag.load(Ordering::SeqCst));
307    }
308
309    #[test]
310    fn test_drop_does_not_call_cleanup_if_already_closed() {
311        let flag = Arc::new(AtomicBool::new(false));
312        let flag_clone = flag.clone();
313        let resource_ptr = make_resource(30);
314
315        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
316            flag_clone.store(true, Ordering::SeqCst);
317            unsafe {
318                *res = std::ptr::null_mut();
319            }
320            0
321        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
322
323        let mut resource = ManagedCResource::new(
324            |res: *mut *mut i32| {
325                unsafe {
326                    *res = resource_ptr;
327                }
328                0
329            },
330            cleanup,
331            false,
332            None,
333        );
334        assert!(resource.is_ok());
335
336        if let Ok(ref mut resource) = &mut resource {
337            assert!(resource.close().is_ok())
338        }
339
340        // Reset the flag to ensure drop does not call cleanup a second time.
341        flag.store(false, Ordering::SeqCst);
342        drop(resource);
343        assert!(!flag.load(Ordering::SeqCst));
344    }
345
346    #[test]
347    fn test_drop_does_not_call_cleanup_if_check_for_is_closed_returns_true() {
348        let flag = Arc::new(AtomicBool::new(false));
349        let flag_clone = flag.clone();
350        let resource_ptr = make_resource(60);
351
352        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
353            flag_clone.store(true, Ordering::SeqCst);
354            unsafe {
355                *res = std::ptr::null_mut();
356            }
357            0
358        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
359
360        let check_fn = Some(|_res: *mut i32| -> bool { true } as fn(_) -> bool);
361
362        {
363            let _resource = ManagedCResource::new(
364                |res: *mut *mut i32| {
365                    unsafe {
366                        *res = resource_ptr;
367                    }
368                    0
369                },
370                cleanup,
371                false,
372                check_fn,
373            );
374            assert!(_resource.is_ok());
375        }
376        assert!(!flag.load(Ordering::SeqCst));
377    }
378
379    #[test]
380    fn test_drop_does_call_cleanup_if_check_for_is_closed_returns_false() {
381        let flag = Arc::new(AtomicBool::new(false));
382        let flag_clone = flag.clone();
383        let resource_ptr = make_resource(60);
384
385        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
386            flag_clone.store(true, Ordering::SeqCst);
387            unsafe {
388                *res = std::ptr::null_mut();
389            }
390            0
391        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
392
393        let check_fn = Some(|_res: *mut i32| -> bool { false } as fn(*mut i32) -> bool);
394
395        {
396            let _resource = ManagedCResource::new(
397                |res: *mut *mut i32| {
398                    unsafe {
399                        *res = resource_ptr;
400                    }
401                    0
402                },
403                cleanup,
404                false,
405                check_fn,
406            );
407            assert!(_resource.is_ok())
408        }
409        assert!(flag.load(Ordering::SeqCst));
410    }
411}