From 7b1b6c362dd9d13a5f6353d1db2a777e375143be Mon Sep 17 00:00:00 2001 From: mbyx Date: Thu, 12 Jun 2025 16:09:36 +0500 Subject: [PATCH] Add macro expansion and ctest entrypoint. --- ci/run.sh | 2 +- ctest-next/src/generator.rs | 26 ++++++++++++++ ctest-next/src/lib.rs | 33 +++++++++-------- ctest-next/src/macro_expansion.rs | 23 ++++++++++++ ctest-next/tests/basic.rs | 39 +++++++++++++++++++++ ctest-next/tests/input/hierarchy/bar/mod.rs | 2 ++ ctest-next/tests/input/hierarchy/foo.rs | 10 ++++++ ctest-next/tests/input/hierarchy/lib.rs | 4 +++ ctest-next/tests/input/invalid_syntax.rs | 9 +++++ ctest-next/tests/input/macro.rs | 12 +++++++ ctest-next/tests/input/simple.rs | 15 ++++++++ 11 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 ctest-next/src/generator.rs create mode 100644 ctest-next/src/macro_expansion.rs create mode 100644 ctest-next/tests/basic.rs create mode 100644 ctest-next/tests/input/hierarchy/bar/mod.rs create mode 100644 ctest-next/tests/input/hierarchy/foo.rs create mode 100644 ctest-next/tests/input/hierarchy/lib.rs create mode 100644 ctest-next/tests/input/invalid_syntax.rs create mode 100644 ctest-next/tests/input/macro.rs create mode 100644 ctest-next/tests/input/simple.rs diff --git a/ci/run.sh b/ci/run.sh index 69013e204d148..2144e9823c0ab 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -95,7 +95,7 @@ esac # garando_errors only compiles on `cfg(any(unix, windows))` case "$target" in - *wasm*) cmd="$cmd --exclude ctest --exclude ctest-test" + *wasm*) cmd="$cmd --exclude ctest --exclude ctest-test --exclude ctest-next" esac # # FIXME(ctest): duplicate symbol errors for statics, e.g. T1_static_mut_u8, on Unix- diff --git a/ctest-next/src/generator.rs b/ctest-next/src/generator.rs new file mode 100644 index 0000000000000..b5cc95818e251 --- /dev/null +++ b/ctest-next/src/generator.rs @@ -0,0 +1,26 @@ +use std::path::Path; + +use crate::{expand, Result}; + +/// A builder used to generate a test suite. +#[non_exhaustive] +pub struct TestGenerator {} + +impl Default for TestGenerator { + fn default() -> Self { + Self::new() + } +} + +impl TestGenerator { + /// Creates a new blank test generator. + pub fn new() -> Self { + Self {} + } + + /// Generate all tests for the given crate and output the Rust side to a file. + pub fn generate>(&self, crate_path: P, _output_file_path: P) -> Result<()> { + let _expanded = expand(crate_path)?; + Ok(()) + } +} diff --git a/ctest-next/src/lib.rs b/ctest-next/src/lib.rs index 7d12d9af8195b..37ea15946b8e9 100644 --- a/ctest-next/src/lib.rs +++ b/ctest-next/src/lib.rs @@ -1,14 +1,19 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +#![warn(missing_docs)] +#![warn(unreachable_pub)] + +//! # ctest2 - an FFI binding validator +//! +//! This library is intended to be used as a build dependency in a separate +//! project from the main repo to generate tests which can be used to validate +//! FFI bindings in Rust against the headers from which they come from. + +mod generator; +mod macro_expansion; + +pub use generator::TestGenerator; +pub use macro_expansion::expand; + +/// A possible error that can be encountered in our library. +pub type Error = Box; +/// A type alias for `std::result::Result` that defaults to our error type. +pub type Result = std::result::Result; diff --git a/ctest-next/src/macro_expansion.rs b/ctest-next/src/macro_expansion.rs new file mode 100644 index 0000000000000..c41ad6c71b2c5 --- /dev/null +++ b/ctest-next/src/macro_expansion.rs @@ -0,0 +1,23 @@ +use std::{env, fs::canonicalize, path::Path, process::Command}; + +use crate::Result; + +/// Use rustc to expand all macros and pretty print the crate into a single file. +pub fn expand>(crate_path: P) -> Result { + let rustc = env::var("RUSTC").unwrap_or_else(|_| String::from("rustc")); + + let output = Command::new(rustc) + .env("RUSTC_BOOTSTRAP", "1") + .arg("-Zunpretty=expanded") + .arg(canonicalize(crate_path)?) + .output()?; + + if !output.status.success() { + let error = std::str::from_utf8(&output.stderr)?; + return Err(error.into()); + } + + let expanded = std::str::from_utf8(&output.stdout)?.to_string(); + + Ok(expanded) +} diff --git a/ctest-next/tests/basic.rs b/ctest-next/tests/basic.rs new file mode 100644 index 0000000000000..1662c0e50d78c --- /dev/null +++ b/ctest-next/tests/basic.rs @@ -0,0 +1,39 @@ +use ctest_next::TestGenerator; + +#[test] +fn test_entrypoint_hierarchy() { + let generator = TestGenerator::new(); + + generator + .generate("./tests/input/hierarchy/lib.rs", "hierarchy_out.rs") + .unwrap(); +} + +#[test] +fn test_entrypoint_simple() { + let generator = TestGenerator::new(); + + generator + .generate("./tests/input/simple.rs", "simple_out.rs") + .unwrap(); +} + +#[test] +fn test_entrypoint_macro() { + let generator = TestGenerator::new(); + + generator + .generate("./tests/input/macro.rs", "macro_out.rs") + .unwrap(); +} + +#[test] +fn test_entrypoint_invalid_syntax() { + let generator = TestGenerator::new(); + + let fails = generator + .generate("./tests/input/invalid_syntax.rs", "invalid_syntax_out.rs") + .is_err(); + + assert!(fails) +} diff --git a/ctest-next/tests/input/hierarchy/bar/mod.rs b/ctest-next/tests/input/hierarchy/bar/mod.rs new file mode 100644 index 0000000000000..99c3027191e4a --- /dev/null +++ b/ctest-next/tests/input/hierarchy/bar/mod.rs @@ -0,0 +1,2 @@ +#[allow(non_camel_case_types)] +pub type in6_addr = u32; diff --git a/ctest-next/tests/input/hierarchy/foo.rs b/ctest-next/tests/input/hierarchy/foo.rs new file mode 100644 index 0000000000000..571b7947c4c52 --- /dev/null +++ b/ctest-next/tests/input/hierarchy/foo.rs @@ -0,0 +1,10 @@ +use crate::bar::in6_addr; +use std::os::raw::c_void; + +pub const ON: bool = true; + +unsafe extern "C" { + fn malloc(size: usize) -> *mut c_void; + + static in6addr_any: in6_addr; +} diff --git a/ctest-next/tests/input/hierarchy/lib.rs b/ctest-next/tests/input/hierarchy/lib.rs new file mode 100644 index 0000000000000..6c840d79bac21 --- /dev/null +++ b/ctest-next/tests/input/hierarchy/lib.rs @@ -0,0 +1,4 @@ +//! Ensure that our crate is able to handle definitions spread across many files + +mod bar; +mod foo; diff --git a/ctest-next/tests/input/invalid_syntax.rs b/ctest-next/tests/input/invalid_syntax.rs new file mode 100644 index 0000000000000..25191d43a2df2 --- /dev/null +++ b/ctest-next/tests/input/invalid_syntax.rs @@ -0,0 +1,9 @@ +struct Foo { + a: int, + b: u8; +} + +unin Bar { + x: u8, + y: u8 +} diff --git a/ctest-next/tests/input/macro.rs b/ctest-next/tests/input/macro.rs new file mode 100644 index 0000000000000..d0ce80180663f --- /dev/null +++ b/ctest-next/tests/input/macro.rs @@ -0,0 +1,12 @@ +macro_rules! vector { + ($name:ident, $ty:ty) => { + #[repr(C)] + struct $name { + x: $ty, + y: $ty, + } + }; +} + +vector!(VecU8, u8); +vector!(VecU16, u16); diff --git a/ctest-next/tests/input/simple.rs b/ctest-next/tests/input/simple.rs new file mode 100644 index 0000000000000..e62b4e927dd8a --- /dev/null +++ b/ctest-next/tests/input/simple.rs @@ -0,0 +1,15 @@ +use std::os::raw::c_char; + +pub type Byte = u8; + +#[repr(C)] +pub struct Person { + name: *const c_char, + age: u8, +} + +#[repr(C)] +pub union Word { + word: u16, + byte: [Byte; 2], +}