rusteron_code_gen/
lib.rs

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