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
238    use std::sync::atomic::{AtomicBool, Ordering};
239    use std::sync::Arc;
240
241    fn make_resource(val: i32) -> *mut i32 {
242        Box::into_raw(Box::new(val))
243    }
244
245    #[test]
246    fn test_drop_calls_cleanup_non_borrowed_no_cleanup_struct() {
247        let flag = Arc::new(AtomicBool::new(false));
248        let flag_clone = flag.clone();
249        let resource_ptr = make_resource(10);
250
251        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
252            flag_clone.store(true, Ordering::SeqCst);
253            // Set the resource to null to simulate cleanup.
254            unsafe {
255                *res = std::ptr::null_mut();
256            }
257            0
258        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
259
260        {
261            let _resource = ManagedCResource::new(
262                |res: *mut *mut i32| {
263                    unsafe {
264                        *res = resource_ptr;
265                    }
266                    0
267                },
268                cleanup,
269                false,
270                None,
271            );
272            assert!(_resource.is_ok())
273        }
274        assert!(flag.load(Ordering::SeqCst));
275    }
276
277    #[test]
278    fn test_drop_calls_cleanup_non_borrowed_with_cleanup_struct() {
279        let flag = Arc::new(AtomicBool::new(false));
280        let flag_clone = flag.clone();
281        let resource_ptr = make_resource(20);
282
283        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
284            flag_clone.store(true, Ordering::SeqCst);
285            unsafe {
286                *res = std::ptr::null_mut();
287            }
288            0
289        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
290
291        {
292            let _resource = ManagedCResource::new(
293                |res: *mut *mut i32| {
294                    unsafe {
295                        *res = resource_ptr;
296                    }
297                    0
298                },
299                cleanup,
300                true,
301                None,
302            );
303            assert!(_resource.is_ok())
304        }
305        assert!(flag.load(Ordering::SeqCst));
306    }
307
308    #[test]
309    fn test_drop_does_not_call_cleanup_if_already_closed() {
310        let flag = Arc::new(AtomicBool::new(false));
311        let flag_clone = flag.clone();
312        let resource_ptr = make_resource(30);
313
314        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
315            flag_clone.store(true, Ordering::SeqCst);
316            unsafe {
317                *res = std::ptr::null_mut();
318            }
319            0
320        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
321
322        let mut resource = ManagedCResource::new(
323            |res: *mut *mut i32| {
324                unsafe {
325                    *res = resource_ptr;
326                }
327                0
328            },
329            cleanup,
330            false,
331            None,
332        );
333        assert!(resource.is_ok());
334
335        if let Ok(ref mut resource) = &mut resource {
336            assert!(resource.close().is_ok())
337        }
338
339        // Reset the flag to ensure drop does not call cleanup a second time.
340        flag.store(false, Ordering::SeqCst);
341        drop(resource);
342        assert!(!flag.load(Ordering::SeqCst));
343    }
344
345    #[test]
346    fn test_drop_does_not_call_cleanup_if_check_for_is_closed_returns_true() {
347        let flag = Arc::new(AtomicBool::new(false));
348        let flag_clone = flag.clone();
349        let resource_ptr = make_resource(60);
350
351        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
352            flag_clone.store(true, Ordering::SeqCst);
353            unsafe {
354                *res = std::ptr::null_mut();
355            }
356            0
357        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
358
359        let check_fn = Some(|_res: *mut i32| -> bool { true } as fn(_) -> bool);
360
361        {
362            let _resource = ManagedCResource::new(
363                |res: *mut *mut i32| {
364                    unsafe {
365                        *res = resource_ptr;
366                    }
367                    0
368                },
369                cleanup,
370                false,
371                check_fn,
372            );
373            assert!(_resource.is_ok());
374        }
375        assert!(!flag.load(Ordering::SeqCst));
376    }
377
378    #[test]
379    fn test_drop_does_call_cleanup_if_check_for_is_closed_returns_false() {
380        let flag = Arc::new(AtomicBool::new(false));
381        let flag_clone = flag.clone();
382        let resource_ptr = make_resource(60);
383
384        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
385            flag_clone.store(true, Ordering::SeqCst);
386            unsafe {
387                *res = std::ptr::null_mut();
388            }
389            0
390        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
391
392        let check_fn = Some(|_res: *mut i32| -> bool { false } as fn(*mut i32) -> bool);
393
394        {
395            let _resource = ManagedCResource::new(
396                |res: *mut *mut i32| {
397                    unsafe {
398                        *res = resource_ptr;
399                    }
400                    0
401                },
402                cleanup,
403                false,
404                check_fn,
405            );
406            assert!(_resource.is_ok())
407        }
408        assert!(flag.load(Ordering::SeqCst));
409    }
410}