diff --git a/Cargo.lock b/Cargo.lock index a6eb90ac30366..7ea29e12cf4c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,8 +147,7 @@ name = "ctest-test" version = "0.1.0" dependencies = [ "cc", - "cfg-if 1.0.1", - "ctest", + "ctest-next", "libc 1.0.0-alpha.1", ] diff --git a/ctest-next/Cargo.lock b/ctest-next/Cargo.lock new file mode 100644 index 0000000000000..388fd3b08cb6d --- /dev/null +++ b/ctest-next/Cargo.lock @@ -0,0 +1,485 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow", +] + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "cc" +version = "1.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "ctest-next" +version = "0.1.0" +dependencies = [ + "askama", + "cc", + "pretty_assertions", + "proc-macro2", + "quote", + "syn", + "tempfile", + "thiserror", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/ctest-next/src/ast/constant.rs b/ctest-next/src/ast/constant.rs index 654d691df66d5..17c07e989bdc9 100644 --- a/ctest-next/src/ast/constant.rs +++ b/ctest-next/src/ast/constant.rs @@ -3,7 +3,6 @@ use crate::BoxStr; /// Represents a constant variable defined in Rust. #[derive(Debug, Clone)] pub struct Const { - #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, pub(crate) ty: syn::Type, diff --git a/ctest-next/src/ast/field.rs b/ctest-next/src/ast/field.rs index 4645a91f8b50e..9f14812e11ace 100644 --- a/ctest-next/src/ast/field.rs +++ b/ctest-next/src/ast/field.rs @@ -3,7 +3,6 @@ use crate::BoxStr; /// Represents a field in a struct or union defined in Rust. #[derive(Debug, Clone)] pub struct Field { - #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, pub(crate) ty: syn::Type, diff --git a/ctest-next/src/ast/function.rs b/ctest-next/src/ast/function.rs index ac41c702e5489..8722c2b82b740 100644 --- a/ctest-next/src/ast/function.rs +++ b/ctest-next/src/ast/function.rs @@ -5,11 +5,11 @@ use crate::{Abi, BoxStr, Parameter}; /// This structure is only used for parsing functions in extern blocks. #[derive(Debug, Clone)] pub struct Fn { - #[expect(unused)] pub(crate) public: bool, #[expect(unused)] pub(crate) abi: Abi, pub(crate) ident: BoxStr, + pub(crate) link_name: Option, #[expect(unused)] pub(crate) parameters: Vec, #[expect(unused)] @@ -21,4 +21,9 @@ impl Fn { pub fn ident(&self) -> &str { &self.ident } + + /// Return the name of the function to be linked C side with. + pub fn link_name(&self) -> Option<&str> { + self.link_name.as_deref() + } } diff --git a/ctest-next/src/ast/static_variable.rs b/ctest-next/src/ast/static_variable.rs index aa0ec664dc9e1..87219cdadd130 100644 --- a/ctest-next/src/ast/static_variable.rs +++ b/ctest-next/src/ast/static_variable.rs @@ -6,7 +6,6 @@ use crate::{Abi, BoxStr}; /// as a result it does not have a field for storing the expression. #[derive(Debug, Clone)] pub struct Static { - #[expect(unused)] pub(crate) public: bool, #[expect(unused)] pub(crate) abi: Abi, diff --git a/ctest-next/src/ast/structure.rs b/ctest-next/src/ast/structure.rs index 647f9e7053201..346e942bbd564 100644 --- a/ctest-next/src/ast/structure.rs +++ b/ctest-next/src/ast/structure.rs @@ -3,10 +3,8 @@ use crate::{BoxStr, Field}; /// Represents a struct defined in Rust. #[derive(Debug, Clone)] pub struct Struct { - #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, - #[expect(unused)] pub(crate) fields: Vec, } @@ -15,4 +13,9 @@ impl Struct { pub fn ident(&self) -> &str { &self.ident } + + /// Return the public fields of the struct. + pub fn public_fields(&self) -> impl Iterator { + self.fields.iter().filter(|f| f.public) + } } diff --git a/ctest-next/src/ast/type_alias.rs b/ctest-next/src/ast/type_alias.rs index f83992c9a7165..4947e934dde51 100644 --- a/ctest-next/src/ast/type_alias.rs +++ b/ctest-next/src/ast/type_alias.rs @@ -3,7 +3,6 @@ use crate::BoxStr; /// Represents a type alias defined in Rust. #[derive(Debug, Clone)] pub struct Type { - #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, pub(crate) ty: syn::Type, diff --git a/ctest-next/src/ast/union.rs b/ctest-next/src/ast/union.rs index caf0e30eb95a7..96016c77a0f6a 100644 --- a/ctest-next/src/ast/union.rs +++ b/ctest-next/src/ast/union.rs @@ -6,7 +6,6 @@ pub struct Union { #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, - #[expect(unused)] pub(crate) fields: Vec, } @@ -15,4 +14,9 @@ impl Union { pub fn ident(&self) -> &str { &self.ident } + + /// Return the public fields of the union. + pub(crate) fn public_fields(&self) -> impl Iterator { + self.fields.iter().filter(|f| f.public) + } } diff --git a/ctest-next/src/ffi_items.rs b/ctest-next/src/ffi_items.rs index db3dd12487cde..b02466e98d89e 100644 --- a/ctest-next/src/ffi_items.rs +++ b/ctest-next/src/ffi_items.rs @@ -37,7 +37,6 @@ impl FfiItems { } /// Return a list of all type aliases found. - #[cfg_attr(not(test), expect(unused))] pub(crate) fn aliases(&self) -> &Vec { &self.aliases } @@ -123,10 +122,32 @@ fn visit_foreign_item_fn(table: &mut FfiItems, i: &syn::ForeignItemFn, abi: &Abi syn::ReturnType::Type(_, ty) => Some(ty.deref().clone()), }; + let mut link_name_iter = i + .attrs + .iter() + .filter(|attr| attr.path().is_ident("link_name")); + + let link_name = link_name_iter.next().and_then(|attr| match &attr.meta { + syn::Meta::NameValue(nv) => { + if let syn::Expr::Lit(expr_lit) = &nv.value { + if let syn::Lit::Str(lit_str) = &expr_lit.lit { + return Some(lit_str.value().into_boxed_str()); + } + } + None + } + _ => None, + }); + + if link_name_iter.next().is_some() { + panic!("multiple #[link_name = ...] attributes found"); + } + table.foreign_functions.push(Fn { public, abi, ident, + link_name, parameters, return_type, }); diff --git a/ctest-next/src/generator.rs b/ctest-next/src/generator.rs index d4d2b56b12ecc..7d586a89a0779 100644 --- a/ctest-next/src/generator.rs +++ b/ctest-next/src/generator.rs @@ -10,7 +10,8 @@ use thiserror::Error; use crate::ffi_items::FfiItems; use crate::template::{CTestTemplate, RustTestTemplate}; use crate::{ - expand, Const, Field, MapInput, Parameter, Result, Static, Struct, Type, VolatileItemKind, + expand, Const, Field, MapInput, Parameter, Result, Static, Struct, Type, Union, + VolatileItemKind, }; /// A function that takes a mappable input and returns its mapping as Some, otherwise @@ -22,6 +23,8 @@ type Skip = Box bool>; type VolatileItem = Box bool>; /// A function that determines whether a function arument is an array. type ArrayArg = Box bool>; +/// A function that determines whether to skip round trip testing, taking in the identifier name. +type SkipRoundTrip = Box bool>; /// A builder used to generate a test suite. #[derive(Default)] @@ -34,10 +37,12 @@ pub struct TestGenerator { flags: Vec, defines: Vec<(String, Option)>, mapped_names: Vec, - skips: Vec, + pub(crate) volatile_items: Vec, + pub(crate) skips: Vec, verbose_skip: bool, - volatile_items: Vec, - array_arg: Option, + pub(crate) skip_private: bool, + pub(crate) array_arg: Option, + pub(crate) skip_roundtrip: Option, } #[derive(Debug, Error)] @@ -50,8 +55,8 @@ pub enum GenerationError { TemplateRender(String, String), #[error("unable to create or write template file: {0}")] OsError(std::io::Error), - #[error("unable to map Rust identifier or type")] - ItemMap, + #[error("one of {0} environment variable(s) not set")] + EnvVarNotFound(String), } impl TestGenerator { @@ -135,6 +140,21 @@ impl TestGenerator { self } + /// Non public items are not tested if `skip` is `true`. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_private(true); + /// ``` + pub fn skip_private(&mut self, skip: bool) -> &mut Self { + self.skip_private = skip; + self + } + /// Skipped item names are printed to `stderr` if `skip` is `true`. /// /// # Examples @@ -293,7 +313,7 @@ impl TestGenerator { self } - /// Configures whether all tests for a field are skipped or not. + /// Configures whether all tests for a struct field are skipped or not. /// /// # Examples /// @@ -301,13 +321,16 @@ impl TestGenerator { /// use ctest_next::TestGenerator; /// /// let mut cfg = TestGenerator::new(); - /// cfg.skip_field(|s, f| { + /// cfg.skip_struct_field(|s, f| { /// s.ident() == "foo_t" || (s.ident() == "bar_t" && f.ident() == "bar") /// }); /// ``` - pub fn skip_field(&mut self, f: impl Fn(&Struct, &Field) -> bool + 'static) -> &mut Self { + pub fn skip_struct_field( + &mut self, + f: impl Fn(&Struct, &Field) -> bool + 'static, + ) -> &mut Self { self.skips.push(Box::new(move |item| { - if let MapInput::Field(struct_, field) = item { + if let MapInput::StructField(struct_, field) = item { f(struct_, field) } else { false @@ -316,6 +339,93 @@ impl TestGenerator { self } + /// Configures whether all tests for a union field are skipped or not. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_union_field(|s, f| { + /// s.ident() == "foo_t" || (s.ident() == "bar_t" && f.ident() == "bar") + /// }); + /// ``` + pub fn skip_union_field(&mut self, f: impl Fn(&Union, &Field) -> bool + 'static) -> &mut Self { + self.skips.push(Box::new(move |item| { + if let MapInput::UnionField(union_, field) = item { + f(union_, field) + } else { + false + } + })); + self + } + + /// Configures whether tests for the type of a field is skipped or not. + /// + /// The closure is given a Rust struct as well as a field within that + /// struct. A flag indicating whether the field's type should be tested is + /// returned. + /// + /// By default all field properties are tested. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_struct_field_type(|s, field| { + /// s.ident() == "foo_t" || (s.ident() == "bar_t" && field.ident() == "bar") + /// }); + /// ``` + pub fn skip_struct_field_type( + &mut self, + f: impl Fn(&Struct, &Field) -> bool + 'static, + ) -> &mut Self { + self.skips.push(Box::new(move |item| { + if let MapInput::StructFieldType(struct_, field) = item { + f(struct_, field) + } else { + false + } + })); + self + } + + /// Configures whether tests for the type of a field is skipped or not. + /// + /// The closure is given a Rust union as well as a field within that + /// union. A flag indicating whether the field's type should be tested is + /// returned. + /// + /// By default all field properties are tested. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_union_field_type(|s, field| { + /// s.ident() == "foo_t" || (s.ident() == "bar_t" && field.ident() == "bar") + /// }); + /// ``` + pub fn skip_union_field_type( + &mut self, + f: impl Fn(&Union, &Field) -> bool + 'static, + ) -> &mut Self { + self.skips.push(Box::new(move |item| { + if let MapInput::UnionFieldType(union_, field) = item { + f(union_, field) + } else { + false + } + })); + self + } + /// Configures whether all tests for a typedef are skipped or not. /// /// # Examples @@ -447,6 +557,17 @@ impl TestGenerator { } /// Configures how Rust `const`s names are translated to C. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.rename_constant(|c| { + /// (c.ident() == "FOO").then_some("BAR".to_string()) + /// }); + /// ``` pub fn rename_constant(&mut self, f: impl Fn(&Const) -> Option + 'static) -> &mut Self { self.mapped_names.push(Box::new(move |item| { if let MapInput::Const(c) = item { @@ -466,16 +587,16 @@ impl TestGenerator { /// use ctest_next::TestGenerator; /// /// let mut cfg = TestGenerator::new(); - /// cfg.rename_field(|_s, field| { + /// cfg.rename_struct_field(|_s, field| { /// Some(field.ident().replace("foo", "bar")) /// }); /// ``` - pub fn rename_field( + pub fn rename_struct_field( &mut self, f: impl Fn(&Struct, &Field) -> Option + 'static, ) -> &mut Self { self.mapped_names.push(Box::new(move |item| { - if let MapInput::Field(s, c) = item { + if let MapInput::StructField(s, c) = item { f(s, c) } else { None @@ -484,6 +605,32 @@ impl TestGenerator { self } + /// Configures how a Rust union field is translated to a C union field. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.rename_union_field(|_u, field| { + /// Some(field.ident().replace("foo", "bar")) + /// }); + /// ``` + pub fn rename_union_field( + &mut self, + f: impl Fn(&Union, &Field) -> Option + 'static, + ) -> &mut Self { + self.mapped_names.push(Box::new(move |item| { + if let MapInput::UnionField(u, c) = item { + f(u, c) + } else { + None + } + })); + self + } + /// Configures the name of a function in the generated C code. /// /// # Examples @@ -574,6 +721,29 @@ impl TestGenerator { self } + /// Configures whether the ABI roundtrip tests for a type are emitted. + /// + /// The closure is passed the name of a Rust type and returns whether the + /// tests are generated. + /// + /// By default all types undergo ABI roundtrip tests. Arrays cannot undergo + /// an ABI roundtrip because they cannot be returned by C functions, and + /// have to be manually skipped here. + /// + /// # Examples + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_roundtrip(|s| { + /// s.starts_with("foo_") + /// }); + /// ``` + pub fn skip_roundtrip(&mut self, f: impl Fn(&str) -> bool + 'static) -> &mut Self { + self.skip_roundtrip = Some(Box::new(f)); + self + } + /// Generate the Rust and C testing files. /// /// Returns the path to the generated file. @@ -591,13 +761,14 @@ impl TestGenerator { let mut ffi_items = FfiItems::new(); ffi_items.visit_file(&ast); - // FIXME(ctest): Does not filter out tests for fields. + // Does not filter out tests for fields, that is done in the template. self.filter_ffi_items(&mut ffi_items); let output_directory = self .out_dir .clone() - .unwrap_or_else(|| env::var("OUT_DIR").unwrap().into()); + .or_else(|| env::var("OUT_DIR").ok().map(Into::into)) + .ok_or(GenerationError::EnvVarNotFound("OUT_DIR".to_string()))?; let output_file_path = output_directory.join(output_file_path); // Generate the Rust side of the tests. @@ -629,7 +800,9 @@ impl TestGenerator { } /// Skips entire items such as structs, constants, and aliases from being tested. - /// Does not skip specific tests or specific fields. + /// + /// Does not skip specific tests or specific fields. If `skip_private` is true, + /// it will skip tests for all private items. fn filter_ffi_items(&self, ffi_items: &mut FfiItems) { let verbose = self.verbose_skip; @@ -639,6 +812,7 @@ impl TestGenerator { .$field .extract_if(.., |item| { self.skips.iter().any(|f| f(&MapInput::$variant(item))) + || (self.skip_private && !item.public) }) .collect(); if verbose { @@ -657,21 +831,25 @@ impl TestGenerator { } /// Maps Rust identifiers or types to C counterparts, or defaults to the original name. - pub(crate) fn map<'a>(&self, item: impl Into>) -> Result { + pub(crate) fn map<'a>(&self, item: impl Into>) -> String { let item = item.into(); if let Some(mapped) = self.mapped_names.iter().find_map(|f| f(&item)) { - return Ok(mapped); + return mapped; } - Ok(match item { + match item { MapInput::Const(c) => c.ident().to_string(), MapInput::Fn(f) => f.ident().to_string(), MapInput::Static(s) => s.ident().to_string(), MapInput::Struct(s) => s.ident().to_string(), + MapInput::Union(u) => u.ident().to_string(), MapInput::Alias(t) => t.ident().to_string(), - MapInput::Field(_, f) => f.ident().to_string(), + MapInput::StructField(_, f) => f.ident().to_string(), + MapInput::UnionField(_, f) => f.ident().to_string(), MapInput::StructType(ty) => format!("struct {ty}"), MapInput::UnionType(ty) => format!("union {ty}"), + MapInput::StructFieldType(_, f) => f.ident().to_string(), + MapInput::UnionFieldType(_, f) => f.ident().to_string(), MapInput::Type(ty) => ty.to_string(), - }) + } } } diff --git a/ctest-next/src/lib.rs b/ctest-next/src/lib.rs index 244ef7c792b6d..81e73d0327c2f 100644 --- a/ctest-next/src/lib.rs +++ b/ctest-next/src/lib.rs @@ -36,11 +36,13 @@ type BoxStr = Box; /// /// This is necessary because `ctest` does not parse the header file, so it /// does not know which items are volatile. -#[derive(Debug)] +#[derive(Debug, Clone)] #[non_exhaustive] pub enum VolatileItemKind { /// A struct field. StructField(Struct, Field), + /// A union field. + UnionField(Union, Field), /// An extern static. Static(Static), /// A function argument. @@ -54,9 +56,10 @@ pub enum VolatileItemKind { pub(crate) enum MapInput<'a> { /// This variant is used for renaming the struct identifier. Struct(&'a Struct), + Union(&'a Union), Fn(&'a crate::Fn), - #[expect(unused)] - Field(&'a Struct, &'a Field), + StructField(&'a Struct, &'a Field), + UnionField(&'a Union, &'a Field), Alias(&'a Type), Const(&'a Const), Static(&'a Static), @@ -64,6 +67,8 @@ pub(crate) enum MapInput<'a> { /// This variant is used for renaming the struct type. StructType(&'a str), UnionType(&'a str), + StructFieldType(&'a Struct, &'a Field), + UnionFieldType(&'a Union, &'a Field), } /* The From impls make it easier to write code in the test templates. */ @@ -97,3 +102,9 @@ impl<'a> From<&'a Struct> for MapInput<'a> { MapInput::Struct(s) } } + +impl<'a> From<&'a Union> for MapInput<'a> { + fn from(s: &'a Union) -> Self { + MapInput::Union(s) + } +} diff --git a/ctest-next/src/runner.rs b/ctest-next/src/runner.rs index 5aeaa90c93bcc..3acfbc232daa6 100644 --- a/ctest-next/src/runner.rs +++ b/ctest-next/src/runner.rs @@ -4,6 +4,7 @@ use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Command; +use crate::generator::GenerationError; use crate::{Result, TestGenerator}; /// Generate all tests for the given crate and output the Rust side to a file. @@ -12,16 +13,24 @@ pub fn generate_test( generator: &mut TestGenerator, crate_path: impl AsRef, output_file_path: impl AsRef, -) -> Result { +) -> Result { let output_file_path = generator.generate_files(crate_path, output_file_path)?; // Search for the target and host to build for if specified manually // (generator.target, generator.host), // via build script (TARGET, HOST), or for internal testing (TARGET_PLATFORM, HOST_PLATFORM). - let target = generator.target.clone().unwrap_or_else(|| { - env::var("TARGET").unwrap_or_else(|_| env::var("TARGET_PLATFORM").unwrap()) - }); - let host = env::var("HOST").unwrap_or_else(|_| env::var("HOST_PLATFORM").unwrap()); + let target = generator + .target + .clone() + .or_else(|| env::var("TARGET").ok()) + .or_else(|| env::var("TARGET_PLATFORM").ok()) + .ok_or(GenerationError::EnvVarNotFound( + "TARGET, TARGET_PLATFORM".to_string(), + ))?; + + let host = env::var("HOST") + .or_else(|_| env::var("HOST_PLATFORM")) + .map_err(|_| GenerationError::EnvVarNotFound("HOST, HOST_PLATFORM".to_string()))?; let mut cfg = cc::Build::new(); cfg.file(output_file_path.with_extension("c")); diff --git a/ctest-next/src/template.rs b/ctest-next/src/template.rs index 49b0de0284d40..57637ea6b371a 100644 --- a/ctest-next/src/template.rs +++ b/ctest-next/src/template.rs @@ -1,17 +1,20 @@ +use std::ops::Deref; + use askama::Template; use quote::ToTokens; use crate::ffi_items::FfiItems; -use crate::generator::GenerationError; -use crate::translator::Translator; -use crate::{MapInput, Result, TestGenerator}; +use crate::translator::{translate_expr, Translator}; +use crate::{ + Field, MapInput, Result, Struct, TestGenerator, TranslationError, Union, VolatileItemKind, +}; /// Represents the Rust side of the generated testing suite. #[derive(Template, Clone)] #[template(path = "test.rs")] pub(crate) struct RustTestTemplate<'a> { ffi_items: &'a FfiItems, - #[expect(unused)] + translator: Translator, generator: &'a TestGenerator, } @@ -29,6 +32,7 @@ impl<'a> RustTestTemplate<'a> { pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self { Self { ffi_items, + translator: Translator::new(), generator, } } @@ -45,28 +49,32 @@ impl<'a> CTestTemplate<'a> { } /// Returns the equivalent C/Cpp identifier of the Rust item. - pub(crate) fn c_ident(&self, item: impl Into>) -> Result { + pub(crate) fn c_ident(&self, item: impl Into>) -> String { self.generator.map(item) } /// Returns the equivalent C/Cpp type of the Rust item. - pub(crate) fn c_type(&self, item: impl Into>) -> Result { + pub(crate) fn c_type(&self, item: impl Into>) -> Result { let item: MapInput<'a> = item.into(); let (ident, ty) = match item { - MapInput::Const(c) => (c.ident(), self.translator.translate_type(&c.ty)), - MapInput::Alias(a) => (a.ident(), self.translator.translate_type(&a.ty)), - MapInput::Field(_, f) => (f.ident(), self.translator.translate_type(&f.ty)), - MapInput::Static(s) => (s.ident(), self.translator.translate_type(&s.ty)), + MapInput::Const(c) => (c.ident(), self.translator.translate_type(&c.ty)?), + MapInput::StructField(_, f) => (f.ident(), self.translator.translate_type(&f.ty)?), + MapInput::UnionField(_, f) => (f.ident(), self.translator.translate_type(&f.ty)?), + MapInput::Static(s) => (s.ident(), self.translator.translate_type(&s.ty)?), MapInput::Fn(_) => unimplemented!(), - MapInput::Struct(_) => unimplemented!(), + // For structs/unions/aliases, their type is the same as their identifier. + MapInput::Alias(a) => (a.ident(), a.ident().to_string()), + MapInput::Struct(s) => (s.ident(), s.ident().to_string()), + MapInput::Union(u) => (u.ident(), u.ident().to_string()), + MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"), MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"), + MapInput::StructFieldType(_, _) => panic!("MapInput::StructFieldType is not allowed!"), + MapInput::UnionFieldType(_, _) => panic!("MapInput::UnionFieldType is not allowed!"), MapInput::Type(_) => panic!("MapInput::Type is not allowed!"), }; - let ty = ty.map_err(|e| GenerationError::TemplateRender("C".to_string(), e.to_string()))?; - let item = if self.ffi_items.contains_struct(ident) { MapInput::StructType(&ty) } else if self.ffi_items.contains_union(ident) { @@ -74,6 +82,124 @@ impl<'a> CTestTemplate<'a> { } else { MapInput::Type(&ty) }; - self.generator.map(item) + + Ok(self.generator.map(item)) + } + + /// Modify a C function `signature` that returns a ptr `ty` to be correctly translated. + /// + /// Arrays and Function types in C have different rules for placement, such as array lengths + /// being placed after the parameter list. + pub(crate) fn c_signature( + &self, + ty: &syn::Type, + signature: &str, + ) -> Result { + let new_signature = match ty { + syn::Type::BareFn(f) => { + let (ret, mut args, variadic) = self.translator.translate_signature_partial(f)?; + let abi = f + .abi + .clone() + .unwrap() + .name + .map(|s| s.value()) + .unwrap_or("C".to_string()); + + if variadic { + args.push("...".to_string()); + } else if args.is_empty() { + args.push("void".to_string()); + } + + format!("{}({}**{})({})", ret, abi, signature, args.join(", ")) + } + // Handles up to 2D arrays. + syn::Type::Array(outer) => match outer.elem.deref() { + syn::Type::Array(inner) => format!( + "{}(*{})[{}][{}]", + self.translator.translate_type(inner.elem.deref())?, + signature, + translate_expr(&outer.len), + translate_expr(&inner.len) + ), + _ => format!( + "{}(*{})[{}]", + self.translator.translate_type(outer.elem.deref())?, + signature, + translate_expr(&outer.len) + ), + }, + _ => { + let unmapped_c_type = self.translator.translate_type(ty)?; + let map_input = if self + .ffi_items + .contains_struct(&ty.to_token_stream().to_string()) + { + MapInput::StructType(&unmapped_c_type) + } else if self + .ffi_items + .contains_union(&ty.to_token_stream().to_string()) + { + MapInput::UnionType(&unmapped_c_type) + } else { + MapInput::Type(&unmapped_c_type) + }; + format!("{}* {}", self.generator.map(map_input), signature) + } + }; + + Ok(new_signature) + } + + /// Returns the volatile keyword if the given item is volatile. + pub(crate) fn emit_volatile(&self, v: VolatileItemKind) -> &str { + if !self.generator.volatile_items.is_empty() + && self.generator.volatile_items.iter().any(|f| f(v.clone())) + { + "volatile " + } else { + "" + } } } + +/* Helper functions to make the template code readable. */ + +/// Determine whether a Rust alias/struct/union should have a round trip test. +/// +/// By default all alias/struct/unions are roundtripped. Aliases or fields with arrays should +/// not be part of the roundtrip. +pub(crate) fn should_roundtrip(gen: &TestGenerator, ident: &str) -> bool { + gen.skip_roundtrip.as_ref().is_none_or(|skip| !skip(ident)) +} + +/// Determine whether a struct field should be skipped for tests. +pub(crate) fn should_skip_struct_field(gen: &TestGenerator, s: &Struct, field: &Field) -> bool { + gen.skips + .iter() + .any(|f| f(&MapInput::StructField(s, field))) +} + +/// Determine whether a union field should be skipped for tests. +pub(crate) fn should_skip_union_field(gen: &TestGenerator, u: &Union, field: &Field) -> bool { + gen.skips.iter().any(|f| f(&MapInput::UnionField(u, field))) +} + +/// Determine whether a struct field type should be skipped for tests. +pub(crate) fn should_skip_struct_field_type( + gen: &TestGenerator, + s: &Struct, + field: &Field, +) -> bool { + gen.skips + .iter() + .any(|f| f(&MapInput::StructFieldType(s, field))) +} + +/// Determine whether a union field type should be skipped for tests. +pub(crate) fn should_skip_union_field_type(gen: &TestGenerator, u: &Union, field: &Field) -> bool { + gen.skips + .iter() + .any(|f| f(&MapInput::UnionFieldType(u, field))) +} diff --git a/ctest-next/src/tests.rs b/ctest-next/src/tests.rs index b2c5b3cf47476..31977486fd432 100644 --- a/ctest-next/src/tests.rs +++ b/ctest-next/src/tests.rs @@ -26,6 +26,7 @@ pub struct Array { extern "C" { static baz: u16; + #[link_name = "calloc"] fn malloc(size: usize) -> *mut c_void; } "#; @@ -56,6 +57,11 @@ fn test_extraction_ffi_items() { assert_eq!(collect_idents!(ffi_items.foreign_statics()), ["baz"]); assert_eq!(collect_idents!(ffi_items.structs()), ["Array"]); assert_eq!(collect_idents!(ffi_items.unions()), ["Word"]); + + assert_eq!( + ffi_items.foreign_functions()[0].link_name.as_deref(), + Some("calloc") + ); } #[test] diff --git a/ctest-next/src/translator.rs b/ctest-next/src/translator.rs index e78185c767e86..8c90a89c1110f 100644 --- a/ctest-next/src/translator.rs +++ b/ctest-next/src/translator.rs @@ -10,6 +10,8 @@ use quote::ToTokens; use syn::spanned::Spanned; use thiserror::Error; +use crate::ffi_items::FfiItems; + /// An error that occurs during translation, detailing cause and location. #[derive(Debug, Error)] pub struct TranslationError { @@ -219,7 +221,7 @@ impl Translator { } /// Translate a Rust primitive type into its C equivalent. - fn translate_primitive_type(&self, ty: &syn::Ident) -> String { + pub(crate) fn translate_primitive_type(&self, ty: &syn::Ident) -> String { match ty.to_string().as_str() { "usize" => "size_t".to_string(), "isize" => "ssize_t".to_string(), @@ -306,12 +308,56 @@ impl Translator { } } } + + /// Partially translate a Rust bare function type into its equivalent C type. + /// + /// It returns the translated return type, translated argument types, and whether + /// it is variadic as a tuple. + pub(crate) fn translate_signature_partial( + &self, + signature: &syn::TypeBareFn, + ) -> Result<(String, Vec, bool), TranslationError> { + let args = signature + .inputs + .iter() + .map(|arg| self.translate_type(&arg.ty)) + .collect::, TranslationError>>()?; + let return_type = match &signature.output { + syn::ReturnType::Default => "void".to_string(), + syn::ReturnType::Type(_, ty) => match ty.deref() { + syn::Type::Never(_) => "void".to_string(), + syn::Type::Tuple(tuple) if tuple.elems.is_empty() => "void".to_string(), + _ => self.translate_type(ty.deref())?, + }, + }; + Ok((return_type, args, signature.variadic.is_some())) + } + + /// Determine whether a C type is a signed type. + pub(crate) fn is_signed(&self, ffi_items: &FfiItems, ty: &syn::Type) -> bool { + match ty { + syn::Type::Path(path) => { + let ident = path.path.segments.last().unwrap().ident.clone(); + // The only thing other than a primitive that can be signed is an alias. + if let Some(aliased) = ffi_items.aliases().iter().find(|a| ident == a.ident()) { + return self.is_signed(ffi_items, &aliased.ty); + } + match self.translate_primitive_type(&ident).as_str() { + "char" | "short" | "int" | "long" | "long long" | "int8_t" | "int16_t" + | "int32_t" | "int64_t" | "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" + | "size_t" | "ssize_t" => true, + s => s.starts_with("signed ") || s.starts_with("unsigned "), + } + } + _ => false, + } + } } /// Translate a simple Rust expression to C. /// /// This function will just pass the expression as is in most cases. -fn translate_expr(expr: &syn::Expr) -> String { +pub(crate) fn translate_expr(expr: &syn::Expr) -> String { match expr { syn::Expr::Path(p) => p.path.segments.last().unwrap().ident.to_string(), syn::Expr::Cast(c) => translate_expr(c.expr.deref()), diff --git a/ctest-next/templates/common/test_roundtrip.c b/ctest-next/templates/common/test_roundtrip.c new file mode 100644 index 0000000000000..d97b0bfb9d8c5 --- /dev/null +++ b/ctest-next/templates/common/test_roundtrip.c @@ -0,0 +1,48 @@ +{#- Requires the presence of an `ident` and `c_type` variable. +#} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +{{ c_type }} __test_roundtrip_{{ ident }}( + int32_t rust_size, {{ c_type }} value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof({{ c_type }}); + if (size != rust_size) { + fprintf( + stderr, + "size of {{ c_type }} is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"{{ ident }}\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif \ No newline at end of file diff --git a/ctest-next/templates/common/test_roundtrip.rs b/ctest-next/templates/common/test_roundtrip.rs new file mode 100644 index 0000000000000..b94dc5a0794d5 --- /dev/null +++ b/ctest-next/templates/common/test_roundtrip.rs @@ -0,0 +1,54 @@ + {#- Requires the presence of an `ident` variable +#} + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_{{ ident }}() { + type U = {{ ident }}; + extern "C" { + fn __test_roundtrip_{{ ident }}( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_{{ ident }}(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_{{ ident }}(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"{{ ident }}\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } \ No newline at end of file diff --git a/ctest-next/templates/common/test_size_align.c b/ctest-next/templates/common/test_size_align.c new file mode 100644 index 0000000000000..95f8ae80646a2 --- /dev/null +++ b/ctest-next/templates/common/test_size_align.c @@ -0,0 +1,7 @@ +{#- Requires the presence of an `ident` and `c_type` variable. +#} + +// Return the size of a type. +uint64_t ctest_size_of__{{ident}}(void) { return sizeof({{c_type}}); } + +// Return the alignment of a type. +uint64_t ctest_align_of__{{ident}}(void) { return _Alignof({{c_type}}); } \ No newline at end of file diff --git a/ctest-next/templates/common/test_size_align.rs b/ctest-next/templates/common/test_size_align.rs new file mode 100644 index 0000000000000..7e3caa8d475db --- /dev/null +++ b/ctest-next/templates/common/test_size_align.rs @@ -0,0 +1,18 @@ + {#- Requires the presence of an `ident` variable. +#} + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_{{ ident }}() { + extern "C" { + fn ctest_size_of__{{ ident }}() -> u64; + fn ctest_align_of__{{ ident }}() -> u64; + } + unsafe { + check_same(size_of::<{{ ident }}>() as u64, + ctest_size_of__{{ ident }}(), "{{ ident }} size"); + check_same(align_of::<{{ ident }}>() as u64, + ctest_align_of__{{ ident }}(), "{{ ident }} align"); + } + } \ No newline at end of file diff --git a/ctest-next/templates/test.c b/ctest-next/templates/test.c index ef7f09a3e019b..314c27a568aaa 100644 --- a/ctest-next/templates/test.c +++ b/ctest-next/templates/test.c @@ -12,8 +12,8 @@ {%- for constant in ffi_items.constants() +%} {%- let ident = constant.ident() +%} -{%- let c_type = self.c_type(*constant)? +%} -{%- let c_ident = self.c_ident(*constant)? +%} +{%- let c_type = self.c_type(*constant).unwrap() +%} +{%- let c_ident = self.c_ident(*constant) +%} static {{ c_type }} __test_const_{{ ident }}_val = {{ c_ident }}; @@ -24,3 +24,97 @@ static {{ c_type }} __test_const_{{ ident }}_val = {{ c_ident }}; } {%- endfor +%} +{%- for alias in ffi_items.aliases() +%} +{%- let ident = alias.ident() +%} +{%- let c_type = self.c_type(*alias).unwrap() +%} + +{%- include "common/test_size_align.c" +%} + +{%- if translator.is_signed(ffi_items, alias.ty) +%} + +// Return `1` if the type is signed, otherwise return `0`. +uint32_t ctest_{{ ident }}_is_signed(void) { + return (({{ c_type }}) -1) < 0; +} +{%- endif +%} + +{%- if self::should_roundtrip(generator, ident) +%} + +{%- include "common/test_roundtrip.c" +%} +{%- endif +%} +{%- endfor +%} + +{%- for structure in ffi_items.structs() +%} +{%- let ident = structure.ident() +%} +{%- let c_type = self.c_type(*structure).unwrap() +%} + +{%- include "common/test_size_align.c" +%} + +{%- for field in structure.fields +%} +{%- if !self::should_skip_struct_field(generator, structure, field) +%} +{%- let rust_field_name = field.ident() +%} +{%- let c_field_name = self.c_ident(MapInput::StructField(structure, field)) +%} + +uint64_t ctest_offset_of__{{ ident }}__{{ rust_field_name }}(void) { + return offsetof({{ c_type }}, {{ c_field_name }}); +} + +uint64_t ctest_field_size__{{ ident }}__{{ rust_field_name }}(void) { + {{ c_type }}* foo = NULL; + return sizeof(foo->{{ c_field_name }}); +} + +{%- if !self::should_skip_struct_field_type(generator, structure, field) +%} +{%- let signature = self.c_signature(field.ty, &format!("__test_field_type_{ident}_{rust_field_name}({c_type}* b)")).unwrap() +%} +{%- let volatile = self.emit_volatile(VolatileItemKind::StructField(structure.clone(), field.clone())) +%} + +{{ volatile }}{{ signature }} { + return &b->{{ c_field_name }}; +} +{%- endif +%} +{%- endif +%} +{%- endfor +%} + +{%- if self::should_roundtrip(generator, ident) +%} + +{%- include "common/test_roundtrip.c" +%} +{%- endif +%} +{%- endfor +%} + +{%- for union_ in ffi_items.unions() +%} +{%- let ident = union_.ident() +%} +{%- let c_type = self.c_type(*union_).unwrap() +%} + +{%- include "common/test_size_align.c" +%} + +{%- for field in union_.fields +%} +{%- if !self::should_skip_union_field(generator, union_, field) +%} +{%- let rust_field_name = field.ident() +%} +{%- let c_field_name = self.c_ident(MapInput::UnionField(union_, field)) +%} + +uint64_t ctest_offset_of__{{ ident }}__{{ rust_field_name }}(void) { + return offsetof({{ c_type }}, {{ c_field_name }}); +} + +uint64_t ctest_field_size__{{ ident }}__{{ rust_field_name }}(void) { + {{ c_type }}* foo = NULL; + return sizeof(foo->{{ c_field_name }}); +} + +{%- if !self::should_skip_union_field_type(generator, union_, field) +%} +{%- let signature = self.c_signature(field.ty, &format!("__test_field_type_{ident}_{rust_field_name}({c_type}* b)")).unwrap() +%} +{%- let volatile = self.emit_volatile(VolatileItemKind::UnionField(union_.clone(), field.clone())) +%} + +{{ volatile }}{{ signature }} { + return &b->{{ c_field_name }}; +} +{%- endif +%} +{%- endif +%} +{%- endfor +%} + +{%- if self::should_roundtrip(generator, ident) +%} + +{%- include "common/test_roundtrip.c" +%} +{%- endif +%} +{%- endfor +%} + diff --git a/ctest-next/templates/test.rs b/ctest-next/templates/test.rs index cd8cf4caf2de3..804c3a9c6fda3 100644 --- a/ctest-next/templates/test.rs +++ b/ctest-next/templates/test.rs @@ -5,12 +5,13 @@ /// are not allowed at the top-level, so we hack around this by keeping it /// inside of a module. mod generated_tests { - #![allow(non_snake_case)] + #![allow(non_snake_case, unused_imports)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; + use std::ffi::{CStr, c_char, c_int}; use std::fmt::{Debug, LowerHex}; + use std::mem::{MaybeUninit, offset_of, align_of}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; + use std::{ptr, slice}; use super::*; @@ -57,14 +58,13 @@ mod generated_tests { let val = {{ ident }}; unsafe { let ptr = *__test_const_{{ ident }}(); - let val = CStr::from_ptr(ptr.cast::()); + let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const {{ ident }} not utf8"); - let c = ::std::ffi::CStr::from_ptr(ptr as *const _); + let c = CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const {{ ident }} not utf8"); check_same(val, c, "{{ ident }} string"); } } - {%- else +%} // Test that the value of the constant is the same in both Rust and C. @@ -77,15 +77,245 @@ mod generated_tests { unsafe { let ptr1 = ptr::from_ref(&val).cast::(); let ptr2 = __test_const_{{ ident }}().cast::(); - let ptr1_bytes = slice::from_raw_parts(ptr1, mem::size_of::<{{ ty }}>()); - let ptr2_bytes = slice::from_raw_parts(ptr2, mem::size_of::<{{ ty }}>()); + let ptr1_bytes = slice::from_raw_parts(ptr1, size_of::<{{ ty }}>()); + let ptr2_bytes = slice::from_raw_parts(ptr2, size_of::<{{ ty }}>()); for (i, (&b1, &b2)) in ptr1_bytes.iter().zip(ptr2_bytes.iter()).enumerate() { // HACK: This may read uninitialized data! We do this because // there isn't a good way to recursively iterate all fields. - check_same_hex(b1, b2, &format!("{{ ident }} value at byte {}", i)); + check_same_hex(b1, b2, &format!("{{ ident }} value at byte {i}")); + } + } + } + {%- endif +%} + {%- endfor +%} + + {%- for alias in ffi_items.aliases() +%} + {%- let ident = alias.ident() +%} + + {%- include "common/test_size_align.rs" +%} + + {%- if translator.is_signed(ffi_items, alias.ty) +%} + + /// Test that the aliased type has the same signedness (signed or unsigned) in both Rust and C. + /// + /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value + /// depending on whether a signed or unsigned type is used. This is simply checked on both + /// Rust and C sides to see if they are equal. + pub fn sign_{{ ident }}() { + extern "C" { + fn ctest_{{ ident }}_is_signed() -> u32; + } + let all_ones = !(0 as {{ ident }}); + let all_zeros = 0 as {{ ident }}; + unsafe { + check_same((all_ones < all_zeros) as u32, + ctest_{{ ident }}_is_signed(), "{{ ident }} signed"); + } + } + {%- endif +%} + + {%- if self::should_roundtrip(generator, ident) +%} + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_{{ ident }}() -> [bool; size_of::<{{ ident }}>()] { + [false; size_of::<{{ ident }}>()] + } + + {%- include "common/test_roundtrip.rs" +%} + {%- endif +%} + {%- endfor +%} + + {%- for structure in ffi_items.structs() +%} + {%- let ident = structure.ident() +%} + {%- let fields = structure.public_fields().collect::>() +%} + + {%- include "common/test_size_align.rs" +%} + + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. + pub fn field_offset_size_{{ ident }}() { + {%- for field in structure.public_fields() +%} + {%- if !self::should_skip_struct_field(generator, structure, field) +%} + + extern "C" { + fn ctest_offset_of__{{ ident }}__{{ field.ident() }}() -> u64; + fn ctest_field_size__{{ ident }}__{{ field.ident() }}() -> u64; + } + unsafe { + let uninit_ty = MaybeUninit::<{{ ident }}>::zeroed(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = &raw const ((*uninit_ty).{{ field.ident() }}); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!({{ ident }}, {{ field.ident() }}), + ctest_offset_of__{{ ident }}__{{ field.ident() }}() as usize, + "field offset {{ field.ident() }} of {{ ident }}"); + check_same(size_of_val(&val) as u64, + ctest_field_size__{{ ident }}__{{ field.ident() }}(), + "field size {{ field.ident() }} of {{ ident }}"); + } + {%- if !self::should_skip_struct_field_type(generator, structure, field) +%} + + extern "C" { + fn __test_field_type_{{ ident }}_{{ field.ident() }}(a: *const {{ ident }}) -> *mut u8; + } + unsafe { + let uninit_ty = MaybeUninit::<{{ ident }}>::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).{{ field.ident() }}); + check_same(field_ptr as *mut _, + __test_field_type_{{ ident }}_{{ field.ident() }}(ty_ptr), + "field type {{ field.ident() }} of {{ ident }}"); + } + {%- endif +%} + {%- endif +%} + {%- endfor +%} + } + + {%- if self::should_roundtrip(generator, ident) +%} + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_{{ ident }}() -> [bool; size_of::<{{ ident }}>()] { + // stores (offset, size) for each field + {#- If there is no public field these become unused. +#} + {%- if fields.len() > 0 +%} + let mut v = Vec::<(usize, usize)>::new(); + let bar = MaybeUninit::<{{ ident }}>::zeroed(); + let bar = bar.as_ptr(); + {%- else +%} + let v = Vec::<(usize, usize)>::new(); + {%- endif +%} + {%- for field in fields +%} + unsafe { + let ty_ptr = &raw const ((*bar).{{ field.ident() }}); + let val = ty_ptr.read_unaligned(); + let size = size_of_val(&val); + let off = offset_of!({{ ident }}, {{ field.ident() }}); + v.push((off, size)); + } + {%- endfor +%} + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = [true; size_of::<{{ ident }}>()]; + // Initialize all bytes as: + // - padding if we have fields, this means that only + // the fields will be checked + // - no-padding if we have a type alias: if this + // causes problems the type alias should be skipped + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = false; } } + pad } + + {%- include "common/test_roundtrip.rs" +%} + {%- endif +%} + {%- endfor +%} + + {%- for union_ in ffi_items.unions() +%} + {%- let ident = union_.ident() +%} + {%- let fields = union_.public_fields().collect::>() +%} + + {%- include "common/test_size_align.rs" +%} + + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. + pub fn field_offset_size_{{ ident }}() { + {%- for field in union_.public_fields() +%} + {%- if !self::should_skip_union_field(generator, union_, field) +%} + + extern "C" { + fn ctest_offset_of__{{ ident }}__{{ field.ident() }}() -> u64; + fn ctest_field_size__{{ ident }}__{{ field.ident() }}() -> u64; + } + // Check that the offset and size are the same. + unsafe { + let uninit_ty = MaybeUninit::<{{ ident }}>::zeroed(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = &raw const ((*uninit_ty).{{ field.ident() }}); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!({{ ident }}, {{ field.ident() }}), + ctest_offset_of__{{ ident }}__{{ field.ident() }}() as usize, + "field offset {{ field.ident() }} of {{ ident }}"); + check_same(size_of_val(&val) as u64, + ctest_field_size__{{ ident }}__{{ field.ident() }}(), + "field size {{ field.ident() }} of {{ ident }}"); + } + {%- if !self::should_skip_union_field_type(generator, union_, field) +%} + + extern "C" { + fn __test_field_type_{{ ident }}_{{ field.ident() }}(a: *const {{ ident }}) -> *mut u8; + } + // Check that the type of the field is the same. + unsafe { + let uninit_ty = MaybeUninit::<{{ ident }}>::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).{{ field.ident() }}); + check_same(field_ptr as *mut _, + __test_field_type_{{ ident }}_{{ field.ident() }}(ty_ptr), + "field type {{ field.ident() }} of {{ ident }}"); + } + {%- endif +%} + {%- endif +%} + {%- endfor +%} + } + + {%- if self::should_roundtrip(generator, ident) +%} + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_{{ ident }}() -> [bool; size_of::<{{ ident }}>()] { + // stores (offset, size) for each field + {#- If there is no public field these become unused. +#} + {%- if fields.len() > 0 +%} + let mut v = Vec::<(usize, usize)>::new(); + let bar = MaybeUninit::<{{ ident }}>::zeroed(); + let bar = bar.as_ptr(); + {%- else +%} + let v = Vec::<(usize, usize)>::new(); + {%- endif +%} + {%- for field in fields +%} + unsafe { + let ty_ptr = &raw const ((*bar).{{ field.ident() }}); + let val = ty_ptr.read_unaligned(); + let size = size_of_val(&val); + let off = offset_of!({{ ident }}, {{ field.ident() }}); + v.push((off, size)); + } + {%- endfor +%} + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = [true; size_of::<{{ ident }}>()]; + // Initialize all bytes as: + // - padding if we have fields, this means that only + // the fields will be checked + // - no-padding if we have a type alias: if this + // causes problems the type alias should be skipped + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = false; + } + } + pad + } + + {%- include "common/test_roundtrip.rs" +%} {%- endif +%} {%- endfor +%} } @@ -110,5 +340,34 @@ fn run_all() { {%- for constant in ffi_items.constants() +%} const_{{ constant.ident() }}(); {%- endfor +%} + + {%- for alias in ffi_items.aliases() +%} + {%- let ident = alias.ident() +%} + size_align_{{ ident }}(); + {%- if translator.is_signed(ffi_items, alias.ty) +%} + sign_{{ ident }}(); + {%- endif +%} + {%- if self::should_roundtrip(generator, ident) +%} + roundtrip_{{ ident }}(); + {%- endif +%} + {%- endfor +%} + + {%- for structure in ffi_items.structs() +%} + {%- let ident = structure.ident() +%} + size_align_{{ ident }}(); + field_offset_size_{{ ident }}(); + {%- if self::should_roundtrip(generator, structure.ident()) +%} + roundtrip_{{ ident }}(); + {%- endif +%} + {%- endfor +%} + + {%- for union_ in ffi_items.unions() +%} + {%- let ident = union_.ident() +%} + size_align_{{ ident }}(); + field_offset_size_{{ ident }}(); + {%- if self::should_roundtrip(generator, union_.ident()) +%} + roundtrip_{{ ident }}(); + {%- endif +%} + {%- endfor +%} } diff --git a/ctest-next/tests/input/hierarchy.out.c b/ctest-next/tests/input/hierarchy.out.c index 0574cbc03c6f1..09ce1097ab7e3 100644 --- a/ctest-next/tests/input/hierarchy.out.c +++ b/ctest-next/tests/input/hierarchy.out.c @@ -13,3 +13,61 @@ static bool __test_const_ON_val = ON; bool* __test_const_ON(void) { return &__test_const_ON_val; } + +// Return the size of a type. +uint64_t ctest_size_of__in6_addr(void) { return sizeof(in6_addr); } + +// Return the alignment of a type. +uint64_t ctest_align_of__in6_addr(void) { return _Alignof(in6_addr); } + +// Return `1` if the type is signed, otherwise return `0`. +uint32_t ctest_in6_addr_is_signed(void) { + return ((in6_addr) -1) < 0; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +in6_addr __test_roundtrip_in6_addr( + int32_t rust_size, in6_addr value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(in6_addr); + if (size != rust_size) { + fprintf( + stderr, + "size of in6_addr is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"in6_addr\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/hierarchy.out.rs b/ctest-next/tests/input/hierarchy.out.rs index f301c77caf378..028963ed5b92c 100644 --- a/ctest-next/tests/input/hierarchy.out.rs +++ b/ctest-next/tests/input/hierarchy.out.rs @@ -4,12 +4,13 @@ /// are not allowed at the top-level, so we hack around this by keeping it /// inside of a module. mod generated_tests { - #![allow(non_snake_case)] + #![allow(non_snake_case, unused_imports)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; + use std::ffi::{CStr, c_char, c_int}; use std::fmt::{Debug, LowerHex}; + use std::mem::{MaybeUninit, offset_of, align_of}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; + use std::{ptr, slice}; use super::*; @@ -51,12 +52,110 @@ mod generated_tests { unsafe { let ptr1 = ptr::from_ref(&val).cast::(); let ptr2 = __test_const_ON().cast::(); - let ptr1_bytes = slice::from_raw_parts(ptr1, mem::size_of::()); - let ptr2_bytes = slice::from_raw_parts(ptr2, mem::size_of::()); + let ptr1_bytes = slice::from_raw_parts(ptr1, size_of::()); + let ptr2_bytes = slice::from_raw_parts(ptr2, size_of::()); for (i, (&b1, &b2)) in ptr1_bytes.iter().zip(ptr2_bytes.iter()).enumerate() { // HACK: This may read uninitialized data! We do this because // there isn't a good way to recursively iterate all fields. - check_same_hex(b1, b2, &format!("ON value at byte {}", i)); + check_same_hex(b1, b2, &format!("ON value at byte {i}")); + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_in6_addr() { + extern "C" { + fn ctest_size_of__in6_addr() -> u64; + fn ctest_align_of__in6_addr() -> u64; + } + unsafe { + check_same(size_of::() as u64, + ctest_size_of__in6_addr(), "in6_addr size"); + check_same(align_of::() as u64, + ctest_align_of__in6_addr(), "in6_addr align"); + } + } + + /// Test that the aliased type has the same signedness (signed or unsigned) in both Rust and C. + /// + /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value + /// depending on whether a signed or unsigned type is used. This is simply checked on both + /// Rust and C sides to see if they are equal. + pub fn sign_in6_addr() { + extern "C" { + fn ctest_in6_addr_is_signed() -> u32; + } + let all_ones = !(0 as in6_addr); + let all_zeros = 0 as in6_addr; + unsafe { + check_same((all_ones < all_zeros) as u32, + ctest_in6_addr_is_signed(), "in6_addr signed"); + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_in6_addr() -> [bool; size_of::()] { + [false; size_of::()] + } + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_in6_addr() { + type U = in6_addr; + extern "C" { + fn __test_roundtrip_in6_addr( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_in6_addr(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_in6_addr(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"in6_addr\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } } } } @@ -80,4 +179,7 @@ fn main() { // Run all tests by calling the functions that define them. fn run_all() { const_ON(); + size_align_in6_addr(); + sign_in6_addr(); + roundtrip_in6_addr(); } diff --git a/ctest-next/tests/input/macro.h b/ctest-next/tests/input/macro.h index 2b0ef6b80e351..940ce124b9cf9 100644 --- a/ctest-next/tests/input/macro.h +++ b/ctest-next/tests/input/macro.h @@ -11,3 +11,5 @@ struct VecU16 uint16_t x; uint16_t y; }; + +typedef const char *string; diff --git a/ctest-next/tests/input/macro.out.c b/ctest-next/tests/input/macro.out.c index 736c06b8291bd..1c485bad6bb59 100644 --- a/ctest-next/tests/input/macro.out.c +++ b/ctest-next/tests/input/macro.out.c @@ -5,3 +5,214 @@ #include #include #include + +// Return the size of a type. +uint64_t ctest_size_of__string(void) { return sizeof(string); } + +// Return the alignment of a type. +uint64_t ctest_align_of__string(void) { return _Alignof(string); } + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +string __test_roundtrip_string( + int32_t rust_size, string value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(string); + if (size != rust_size) { + fprintf( + stderr, + "size of string is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"string\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t ctest_size_of__VecU8(void) { return sizeof(struct VecU8); } + +// Return the alignment of a type. +uint64_t ctest_align_of__VecU8(void) { return _Alignof(struct VecU8); } + +uint64_t ctest_offset_of__VecU8__x(void) { + return offsetof(struct VecU8, x); +} + +uint64_t ctest_field_size__VecU8__x(void) { + struct VecU8* foo = NULL; + return sizeof(foo->x); +} + +uint8_t* __test_field_type_VecU8_x(struct VecU8* b) { + return &b->x; +} + +uint64_t ctest_offset_of__VecU8__y(void) { + return offsetof(struct VecU8, y); +} + +uint64_t ctest_field_size__VecU8__y(void) { + struct VecU8* foo = NULL; + return sizeof(foo->y); +} + +uint8_t* __test_field_type_VecU8_y(struct VecU8* b) { + return &b->y; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +struct VecU8 __test_roundtrip_VecU8( + int32_t rust_size, struct VecU8 value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(struct VecU8); + if (size != rust_size) { + fprintf( + stderr, + "size of struct VecU8 is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"VecU8\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t ctest_size_of__VecU16(void) { return sizeof(struct VecU16); } + +// Return the alignment of a type. +uint64_t ctest_align_of__VecU16(void) { return _Alignof(struct VecU16); } + +uint64_t ctest_offset_of__VecU16__x(void) { + return offsetof(struct VecU16, x); +} + +uint64_t ctest_field_size__VecU16__x(void) { + struct VecU16* foo = NULL; + return sizeof(foo->x); +} + +uint16_t* __test_field_type_VecU16_x(struct VecU16* b) { + return &b->x; +} + +uint64_t ctest_offset_of__VecU16__y(void) { + return offsetof(struct VecU16, y); +} + +uint64_t ctest_field_size__VecU16__y(void) { + struct VecU16* foo = NULL; + return sizeof(foo->y); +} + +uint16_t* __test_field_type_VecU16_y(struct VecU16* b) { + return &b->y; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +struct VecU16 __test_roundtrip_VecU16( + int32_t rust_size, struct VecU16 value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(struct VecU16); + if (size != rust_size) { + fprintf( + stderr, + "size of struct VecU16 is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"VecU16\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/macro.out.rs b/ctest-next/tests/input/macro.out.rs index 61c7b4a3a4f91..fea8d66173817 100644 --- a/ctest-next/tests/input/macro.out.rs +++ b/ctest-next/tests/input/macro.out.rs @@ -4,12 +4,13 @@ /// are not allowed at the top-level, so we hack around this by keeping it /// inside of a module. mod generated_tests { - #![allow(non_snake_case)] + #![allow(non_snake_case, unused_imports)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; + use std::ffi::{CStr, c_char, c_int}; use std::fmt::{Debug, LowerHex}; + use std::mem::{MaybeUninit, offset_of, align_of}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; + use std::{ptr, slice}; use super::*; @@ -40,6 +41,287 @@ mod generated_tests { NTESTS.fetch_add(1, Ordering::Relaxed); } } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_string() { + extern "C" { + fn ctest_size_of__string() -> u64; + fn ctest_align_of__string() -> u64; + } + unsafe { + check_same(size_of::() as u64, + ctest_size_of__string(), "string size"); + check_same(align_of::() as u64, + ctest_align_of__string(), "string align"); + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_string() -> [bool; size_of::()] { + [false; size_of::()] + } + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_string() { + type U = string; + extern "C" { + fn __test_roundtrip_string( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_string(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_string(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"string\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_VecU8() { + extern "C" { + fn ctest_size_of__VecU8() -> u64; + fn ctest_align_of__VecU8() -> u64; + } + unsafe { + check_same(size_of::() as u64, + ctest_size_of__VecU8(), "VecU8 size"); + check_same(align_of::() as u64, + ctest_align_of__VecU8(), "VecU8 align"); + } + } + + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. + pub fn field_offset_size_VecU8() { + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_VecU8() -> [bool; size_of::()] { + // stores (offset, size) for each field + let v = Vec::<(usize, usize)>::new(); + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = [true; size_of::()]; + // Initialize all bytes as: + // - padding if we have fields, this means that only + // the fields will be checked + // - no-padding if we have a type alias: if this + // causes problems the type alias should be skipped + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = false; + } + } + pad + } + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_VecU8() { + type U = VecU8; + extern "C" { + fn __test_roundtrip_VecU8( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_VecU8(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_VecU8(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"VecU8\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_VecU16() { + extern "C" { + fn ctest_size_of__VecU16() -> u64; + fn ctest_align_of__VecU16() -> u64; + } + unsafe { + check_same(size_of::() as u64, + ctest_size_of__VecU16(), "VecU16 size"); + check_same(align_of::() as u64, + ctest_align_of__VecU16(), "VecU16 align"); + } + } + + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. + pub fn field_offset_size_VecU16() { + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_VecU16() -> [bool; size_of::()] { + // stores (offset, size) for each field + let v = Vec::<(usize, usize)>::new(); + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = [true; size_of::()]; + // Initialize all bytes as: + // - padding if we have fields, this means that only + // the fields will be checked + // - no-padding if we have a type alias: if this + // causes problems the type alias should be skipped + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = false; + } + } + pad + } + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_VecU16() { + type U = VecU16; + extern "C" { + fn __test_roundtrip_VecU16( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_VecU16(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_VecU16(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"VecU16\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } } use generated_tests::*; @@ -59,4 +341,12 @@ fn main() { // Run all tests by calling the functions that define them. fn run_all() { + size_align_string(); + roundtrip_string(); + size_align_VecU8(); + field_offset_size_VecU8(); + roundtrip_VecU8(); + size_align_VecU16(); + field_offset_size_VecU16(); + roundtrip_VecU16(); } diff --git a/ctest-next/tests/input/macro.rs b/ctest-next/tests/input/macro.rs index d0ce80180663f..67530a5ed194c 100644 --- a/ctest-next/tests/input/macro.rs +++ b/ctest-next/tests/input/macro.rs @@ -1,3 +1,5 @@ +use std::os::raw::c_char; + macro_rules! vector { ($name:ident, $ty:ty) => { #[repr(C)] @@ -10,3 +12,5 @@ macro_rules! vector { vector!(VecU8, u8); vector!(VecU16, u16); + +type string = *const c_char; diff --git a/ctest-next/tests/input/simple.out.c b/ctest-next/tests/input/simple.out.c deleted file mode 100644 index 94df4ec988166..0000000000000 --- a/ctest-next/tests/input/simple.out.c +++ /dev/null @@ -1,15 +0,0 @@ -/* This file was autogenerated by ctest; do not modify directly */ - -#include -#include -#include -#include -#include - -static char const* __test_const_A_val = A; - -// Define a function that returns a pointer to the value of the constant to test. -// This will later be called on the Rust side via FFI. -char const** __test_const_A(void) { - return &__test_const_A_val; -} diff --git a/ctest-next/tests/input/simple.out.rs b/ctest-next/tests/input/simple.out.rs deleted file mode 100644 index ed1daf847f356..0000000000000 --- a/ctest-next/tests/input/simple.out.rs +++ /dev/null @@ -1,79 +0,0 @@ -/* This file was autogenerated by ctest; do not modify directly */ - -/// As this file is sometimes built using rustc, crate level attributes -/// are not allowed at the top-level, so we hack around this by keeping it -/// inside of a module. -mod generated_tests { - #![allow(non_snake_case)] - #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; - use std::fmt::{Debug, LowerHex}; - use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; - - use super::*; - - pub static FAILED: AtomicBool = AtomicBool::new(false); - pub static NTESTS: AtomicUsize = AtomicUsize::new(0); - - /// Check that the value returned from the Rust and C side in a certain test is equivalent. - /// - /// Internally it will remember which checks failed and how many tests have been run. - fn check_same(rust: T, c: T, attr: &str) { - if rust != c { - eprintln!("bad {attr}: rust: {rust:?} != c {c:?}"); - FAILED.store(true, Ordering::Relaxed); - } else { - NTESTS.fetch_add(1, Ordering::Relaxed); - } - } - - /// Check that the value returned from the Rust and C side in a certain test is equivalent. - /// - /// Internally it will remember which checks failed and how many tests have been run. This - /// method is the same as `check_same` but prints errors in bytes in hex. - fn check_same_hex(rust: T, c: T, attr: &str) { - if rust != c { - eprintln!("bad {attr}: rust: {rust:?} ({rust:#x}) != c {c:?} ({c:#x})"); - FAILED.store(true, Ordering::Relaxed); - } else { - NTESTS.fetch_add(1, Ordering::Relaxed); - } - } - // Test that the string constant is the same in both Rust and C. - // While fat pointers can't be translated, we instead of * const c_char. - pub fn const_A() { - extern "C" { - fn __test_const_A() -> *const *const u8; - } - let val = A; - unsafe { - let ptr = *__test_const_A(); - let val = CStr::from_ptr(ptr.cast::()); - let val = val.to_str().expect("const A not utf8"); - let c = ::std::ffi::CStr::from_ptr(ptr as *const _); - let c = c.to_str().expect("const A not utf8"); - check_same(val, c, "A string"); - } - } -} - -use generated_tests::*; - -fn main() { - println!("RUNNING ALL TESTS"); - run_all(); - if FAILED.load(std::sync::atomic::Ordering::Relaxed) { - panic!("some tests failed"); - } else { - println!( - "PASSED {} tests", - NTESTS.load(std::sync::atomic::Ordering::Relaxed) - ); - } -} - -// Run all tests by calling the functions that define them. -fn run_all() { - const_A(); -} diff --git a/ctest-next/tests/input/simple.out.with-renames.c b/ctest-next/tests/input/simple.out.with-renames.c index 8d6794dafe228..3a0c5160ee9e0 100644 --- a/ctest-next/tests/input/simple.out.with-renames.c +++ b/ctest-next/tests/input/simple.out.with-renames.c @@ -21,3 +21,219 @@ static char const* __test_const_B_val = C_B; char const** __test_const_B(void) { return &__test_const_B_val; } + +// Return the size of a type. +uint64_t ctest_size_of__Byte(void) { return sizeof(Byte); } + +// Return the alignment of a type. +uint64_t ctest_align_of__Byte(void) { return _Alignof(Byte); } + +// Return `1` if the type is signed, otherwise return `0`. +uint32_t ctest_Byte_is_signed(void) { + return ((Byte) -1) < 0; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +Byte __test_roundtrip_Byte( + int32_t rust_size, Byte value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(Byte); + if (size != rust_size) { + fprintf( + stderr, + "size of Byte is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Byte\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t ctest_size_of__Person(void) { return sizeof(struct Person); } + +// Return the alignment of a type. +uint64_t ctest_align_of__Person(void) { return _Alignof(struct Person); } + +uint64_t ctest_offset_of__Person__name(void) { + return offsetof(struct Person, name); +} + +uint64_t ctest_field_size__Person__name(void) { + struct Person* foo = NULL; + return sizeof(foo->name); +} + +char const** __test_field_type_Person_name(struct Person* b) { + return &b->name; +} + +uint64_t ctest_offset_of__Person__age(void) { + return offsetof(struct Person, age); +} + +uint64_t ctest_field_size__Person__age(void) { + struct Person* foo = NULL; + return sizeof(foo->age); +} + +uint8_t* __test_field_type_Person_age(struct Person* b) { + return &b->age; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +struct Person __test_roundtrip_Person( + int32_t rust_size, struct Person value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(struct Person); + if (size != rust_size) { + fprintf( + stderr, + "size of struct Person is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Person\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t ctest_size_of__Word(void) { return sizeof(union Word); } + +// Return the alignment of a type. +uint64_t ctest_align_of__Word(void) { return _Alignof(union Word); } + +uint64_t ctest_offset_of__Word__word(void) { + return offsetof(union Word, word); +} + +uint64_t ctest_field_size__Word__word(void) { + union Word* foo = NULL; + return sizeof(foo->word); +} + +uint16_t* __test_field_type_Word_word(union Word* b) { + return &b->word; +} + +uint64_t ctest_offset_of__Word__byte(void) { + return offsetof(union Word, byte); +} + +uint64_t ctest_field_size__Word__byte(void) { + union Word* foo = NULL; + return sizeof(foo->byte); +} + +Byte(*__test_field_type_Word_byte(union Word* b))[2] { + return &b->byte; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +union Word __test_roundtrip_Word( + int32_t rust_size, union Word value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(union Word); + if (size != rust_size) { + fprintf( + stderr, + "size of union Word is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Word\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/simple.out.with-renames.rs b/ctest-next/tests/input/simple.out.with-renames.rs index 06929a6077860..768d967a45532 100644 --- a/ctest-next/tests/input/simple.out.with-renames.rs +++ b/ctest-next/tests/input/simple.out.with-renames.rs @@ -4,12 +4,13 @@ /// are not allowed at the top-level, so we hack around this by keeping it /// inside of a module. mod generated_tests { - #![allow(non_snake_case)] + #![allow(non_snake_case, unused_imports)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; + use std::ffi::{CStr, c_char, c_int}; use std::fmt::{Debug, LowerHex}; + use std::mem::{MaybeUninit, offset_of, align_of}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; + use std::{ptr, slice}; use super::*; @@ -50,9 +51,9 @@ mod generated_tests { let val = A; unsafe { let ptr = *__test_const_A(); - let val = CStr::from_ptr(ptr.cast::()); + let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const A not utf8"); - let c = ::std::ffi::CStr::from_ptr(ptr as *const _); + let c = CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const A not utf8"); check_same(val, c, "A string"); } @@ -67,13 +68,389 @@ mod generated_tests { let val = B; unsafe { let ptr = *__test_const_B(); - let val = CStr::from_ptr(ptr.cast::()); + let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const B not utf8"); - let c = ::std::ffi::CStr::from_ptr(ptr as *const _); + let c = CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const B not utf8"); check_same(val, c, "B string"); } } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Byte() { + extern "C" { + fn ctest_size_of__Byte() -> u64; + fn ctest_align_of__Byte() -> u64; + } + unsafe { + check_same(size_of::() as u64, + ctest_size_of__Byte(), "Byte size"); + check_same(align_of::() as u64, + ctest_align_of__Byte(), "Byte align"); + } + } + + /// Test that the aliased type has the same signedness (signed or unsigned) in both Rust and C. + /// + /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value + /// depending on whether a signed or unsigned type is used. This is simply checked on both + /// Rust and C sides to see if they are equal. + pub fn sign_Byte() { + extern "C" { + fn ctest_Byte_is_signed() -> u32; + } + let all_ones = !(0 as Byte); + let all_zeros = 0 as Byte; + unsafe { + check_same((all_ones < all_zeros) as u32, + ctest_Byte_is_signed(), "Byte signed"); + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Byte() -> [bool; size_of::()] { + [false; size_of::()] + } + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_Byte() { + type U = Byte; + extern "C" { + fn __test_roundtrip_Byte( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_Byte(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Byte(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Byte\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Person() { + extern "C" { + fn ctest_size_of__Person() -> u64; + fn ctest_align_of__Person() -> u64; + } + unsafe { + check_same(size_of::() as u64, + ctest_size_of__Person(), "Person size"); + check_same(align_of::() as u64, + ctest_align_of__Person(), "Person align"); + } + } + + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. + pub fn field_offset_size_Person() { + + extern "C" { + fn ctest_offset_of__Person__name() -> u64; + fn ctest_field_size__Person__name() -> u64; + } + unsafe { + let uninit_ty = MaybeUninit::::zeroed(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = &raw const ((*uninit_ty).name); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!(Person, name), + ctest_offset_of__Person__name() as usize, + "field offset name of Person"); + check_same(size_of_val(&val) as u64, + ctest_field_size__Person__name(), + "field size name of Person"); + } + + extern "C" { + fn __test_field_type_Person_name(a: *const Person) -> *mut u8; + } + unsafe { + let uninit_ty = MaybeUninit::::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).name); + check_same(field_ptr as *mut _, + __test_field_type_Person_name(ty_ptr), + "field type name of Person"); + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Person() -> [bool; size_of::()] { + // stores (offset, size) for each field + let mut v = Vec::<(usize, usize)>::new(); + let bar = MaybeUninit::::zeroed(); + let bar = bar.as_ptr(); + unsafe { + let ty_ptr = &raw const ((*bar).name); + let val = ty_ptr.read_unaligned(); + let size = size_of_val(&val); + let off = offset_of!(Person, name); + v.push((off, size)); + } + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = [true; size_of::()]; + // Initialize all bytes as: + // - padding if we have fields, this means that only + // the fields will be checked + // - no-padding if we have a type alias: if this + // causes problems the type alias should be skipped + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = false; + } + } + pad + } + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_Person() { + type U = Person; + extern "C" { + fn __test_roundtrip_Person( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_Person(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Person(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Person\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Word() { + extern "C" { + fn ctest_size_of__Word() -> u64; + fn ctest_align_of__Word() -> u64; + } + unsafe { + check_same(size_of::() as u64, + ctest_size_of__Word(), "Word size"); + check_same(align_of::() as u64, + ctest_align_of__Word(), "Word align"); + } + } + + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. + pub fn field_offset_size_Word() { + + extern "C" { + fn ctest_offset_of__Word__word() -> u64; + fn ctest_field_size__Word__word() -> u64; + } + // Check that the offset and size are the same. + unsafe { + let uninit_ty = MaybeUninit::::zeroed(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = &raw const ((*uninit_ty).word); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!(Word, word), + ctest_offset_of__Word__word() as usize, + "field offset word of Word"); + check_same(size_of_val(&val) as u64, + ctest_field_size__Word__word(), + "field size word of Word"); + } + + extern "C" { + fn __test_field_type_Word_word(a: *const Word) -> *mut u8; + } + // Check that the type of the field is the same. + unsafe { + let uninit_ty = MaybeUninit::::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).word); + check_same(field_ptr as *mut _, + __test_field_type_Word_word(ty_ptr), + "field type word of Word"); + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Word() -> [bool; size_of::()] { + // stores (offset, size) for each field + let mut v = Vec::<(usize, usize)>::new(); + let bar = MaybeUninit::::zeroed(); + let bar = bar.as_ptr(); + unsafe { + let ty_ptr = &raw const ((*bar).word); + let val = ty_ptr.read_unaligned(); + let size = size_of_val(&val); + let off = offset_of!(Word, word); + v.push((off, size)); + } + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = [true; size_of::()]; + // Initialize all bytes as: + // - padding if we have fields, this means that only + // the fields will be checked + // - no-padding if we have a type alias: if this + // causes problems the type alias should be skipped + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = false; + } + } + pad + } + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_Word() { + type U = Word; + extern "C" { + fn __test_roundtrip_Word( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_Word(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Word(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Word\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } } use generated_tests::*; @@ -95,4 +472,13 @@ fn main() { fn run_all() { const_A(); const_B(); + size_align_Byte(); + sign_Byte(); + roundtrip_Byte(); + size_align_Person(); + field_offset_size_Person(); + roundtrip_Person(); + size_align_Word(); + field_offset_size_Word(); + roundtrip_Word(); } diff --git a/ctest-next/tests/input/simple.out.with-skips.c b/ctest-next/tests/input/simple.out.with-skips.c index 94df4ec988166..5d9c0e5172f4b 100644 --- a/ctest-next/tests/input/simple.out.with-skips.c +++ b/ctest-next/tests/input/simple.out.with-skips.c @@ -13,3 +13,219 @@ static char const* __test_const_A_val = A; char const** __test_const_A(void) { return &__test_const_A_val; } + +// Return the size of a type. +uint64_t ctest_size_of__Byte(void) { return sizeof(Byte); } + +// Return the alignment of a type. +uint64_t ctest_align_of__Byte(void) { return _Alignof(Byte); } + +// Return `1` if the type is signed, otherwise return `0`. +uint32_t ctest_Byte_is_signed(void) { + return ((Byte) -1) < 0; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +Byte __test_roundtrip_Byte( + int32_t rust_size, Byte value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(Byte); + if (size != rust_size) { + fprintf( + stderr, + "size of Byte is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Byte\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t ctest_size_of__Person(void) { return sizeof(struct Person); } + +// Return the alignment of a type. +uint64_t ctest_align_of__Person(void) { return _Alignof(struct Person); } + +uint64_t ctest_offset_of__Person__name(void) { + return offsetof(struct Person, name); +} + +uint64_t ctest_field_size__Person__name(void) { + struct Person* foo = NULL; + return sizeof(foo->name); +} + +char const** __test_field_type_Person_name(struct Person* b) { + return &b->name; +} + +uint64_t ctest_offset_of__Person__age(void) { + return offsetof(struct Person, age); +} + +uint64_t ctest_field_size__Person__age(void) { + struct Person* foo = NULL; + return sizeof(foo->age); +} + +uint8_t* __test_field_type_Person_age(struct Person* b) { + return &b->age; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +struct Person __test_roundtrip_Person( + int32_t rust_size, struct Person value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(struct Person); + if (size != rust_size) { + fprintf( + stderr, + "size of struct Person is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Person\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t ctest_size_of__Word(void) { return sizeof(union Word); } + +// Return the alignment of a type. +uint64_t ctest_align_of__Word(void) { return _Alignof(union Word); } + +uint64_t ctest_offset_of__Word__word(void) { + return offsetof(union Word, word); +} + +uint64_t ctest_field_size__Word__word(void) { + union Word* foo = NULL; + return sizeof(foo->word); +} + +uint16_t* __test_field_type_Word_word(union Word* b) { + return &b->word; +} + +uint64_t ctest_offset_of__Word__byte(void) { + return offsetof(union Word, byte); +} + +uint64_t ctest_field_size__Word__byte(void) { + union Word* foo = NULL; + return sizeof(foo->byte); +} + +Byte(*__test_field_type_Word_byte(union Word* b))[2] { + return &b->byte; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed to C and back to Rust remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +union Word __test_roundtrip_Word( + int32_t rust_size, union Word value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(union Word); + if (size != rust_size) { + fprintf( + stderr, + "size of union Word is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Word\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/simple.out.with-skips.rs b/ctest-next/tests/input/simple.out.with-skips.rs index dac02a72aab6c..ba832f8ad9342 100644 --- a/ctest-next/tests/input/simple.out.with-skips.rs +++ b/ctest-next/tests/input/simple.out.with-skips.rs @@ -4,12 +4,13 @@ /// are not allowed at the top-level, so we hack around this by keeping it /// inside of a module. mod generated_tests { - #![allow(non_snake_case)] + #![allow(non_snake_case, unused_imports)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; + use std::ffi::{CStr, c_char, c_int}; use std::fmt::{Debug, LowerHex}; + use std::mem::{MaybeUninit, offset_of, align_of}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; + use std::{ptr, slice}; use super::*; @@ -50,13 +51,389 @@ mod generated_tests { let val = A; unsafe { let ptr = *__test_const_A(); - let val = CStr::from_ptr(ptr.cast::()); + let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const A not utf8"); - let c = ::std::ffi::CStr::from_ptr(ptr as *const _); + let c = CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const A not utf8"); check_same(val, c, "A string"); } } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Byte() { + extern "C" { + fn ctest_size_of__Byte() -> u64; + fn ctest_align_of__Byte() -> u64; + } + unsafe { + check_same(size_of::() as u64, + ctest_size_of__Byte(), "Byte size"); + check_same(align_of::() as u64, + ctest_align_of__Byte(), "Byte align"); + } + } + + /// Test that the aliased type has the same signedness (signed or unsigned) in both Rust and C. + /// + /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value + /// depending on whether a signed or unsigned type is used. This is simply checked on both + /// Rust and C sides to see if they are equal. + pub fn sign_Byte() { + extern "C" { + fn ctest_Byte_is_signed() -> u32; + } + let all_ones = !(0 as Byte); + let all_zeros = 0 as Byte; + unsafe { + check_same((all_ones < all_zeros) as u32, + ctest_Byte_is_signed(), "Byte signed"); + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Byte() -> [bool; size_of::()] { + [false; size_of::()] + } + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_Byte() { + type U = Byte; + extern "C" { + fn __test_roundtrip_Byte( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_Byte(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Byte(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Byte\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Person() { + extern "C" { + fn ctest_size_of__Person() -> u64; + fn ctest_align_of__Person() -> u64; + } + unsafe { + check_same(size_of::() as u64, + ctest_size_of__Person(), "Person size"); + check_same(align_of::() as u64, + ctest_align_of__Person(), "Person align"); + } + } + + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. + pub fn field_offset_size_Person() { + + extern "C" { + fn ctest_offset_of__Person__name() -> u64; + fn ctest_field_size__Person__name() -> u64; + } + unsafe { + let uninit_ty = MaybeUninit::::zeroed(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = &raw const ((*uninit_ty).name); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!(Person, name), + ctest_offset_of__Person__name() as usize, + "field offset name of Person"); + check_same(size_of_val(&val) as u64, + ctest_field_size__Person__name(), + "field size name of Person"); + } + + extern "C" { + fn __test_field_type_Person_name(a: *const Person) -> *mut u8; + } + unsafe { + let uninit_ty = MaybeUninit::::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).name); + check_same(field_ptr as *mut _, + __test_field_type_Person_name(ty_ptr), + "field type name of Person"); + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Person() -> [bool; size_of::()] { + // stores (offset, size) for each field + let mut v = Vec::<(usize, usize)>::new(); + let bar = MaybeUninit::::zeroed(); + let bar = bar.as_ptr(); + unsafe { + let ty_ptr = &raw const ((*bar).name); + let val = ty_ptr.read_unaligned(); + let size = size_of_val(&val); + let off = offset_of!(Person, name); + v.push((off, size)); + } + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = [true; size_of::()]; + // Initialize all bytes as: + // - padding if we have fields, this means that only + // the fields will be checked + // - no-padding if we have a type alias: if this + // causes problems the type alias should be skipped + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = false; + } + } + pad + } + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_Person() { + type U = Person; + extern "C" { + fn __test_roundtrip_Person( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_Person(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Person(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Person\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Word() { + extern "C" { + fn ctest_size_of__Word() -> u64; + fn ctest_align_of__Word() -> u64; + } + unsafe { + check_same(size_of::() as u64, + ctest_size_of__Word(), "Word size"); + check_same(align_of::() as u64, + ctest_align_of__Word(), "Word align"); + } + } + + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. + pub fn field_offset_size_Word() { + + extern "C" { + fn ctest_offset_of__Word__word() -> u64; + fn ctest_field_size__Word__word() -> u64; + } + // Check that the offset and size are the same. + unsafe { + let uninit_ty = MaybeUninit::::zeroed(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = &raw const ((*uninit_ty).word); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!(Word, word), + ctest_offset_of__Word__word() as usize, + "field offset word of Word"); + check_same(size_of_val(&val) as u64, + ctest_field_size__Word__word(), + "field size word of Word"); + } + + extern "C" { + fn __test_field_type_Word_word(a: *const Word) -> *mut u8; + } + // Check that the type of the field is the same. + unsafe { + let uninit_ty = MaybeUninit::::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).word); + check_same(field_ptr as *mut _, + __test_field_type_Word_word(ty_ptr), + "field type word of Word"); + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Word() -> [bool; size_of::()] { + // stores (offset, size) for each field + let mut v = Vec::<(usize, usize)>::new(); + let bar = MaybeUninit::::zeroed(); + let bar = bar.as_ptr(); + unsafe { + let ty_ptr = &raw const ((*bar).word); + let val = ty_ptr.read_unaligned(); + let size = size_of_val(&val); + let off = offset_of!(Word, word); + v.push((off, size)); + } + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = [true; size_of::()]; + // Initialize all bytes as: + // - padding if we have fields, this means that only + // the fields will be checked + // - no-padding if we have a type alias: if this + // causes problems the type alias should be skipped + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = false; + } + } + pad + } + + /// Tests whether the alias/struct/union `x` when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. + pub fn roundtrip_Word() { + type U = Word; + extern "C" { + fn __test_roundtrip_Word( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_Word(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Word(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Word\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } } use generated_tests::*; @@ -77,4 +454,13 @@ fn main() { // Run all tests by calling the functions that define them. fn run_all() { const_A(); + size_align_Byte(); + sign_Byte(); + roundtrip_Byte(); + size_align_Person(); + field_offset_size_Person(); + roundtrip_Person(); + size_align_Word(); + field_offset_size_Word(); + roundtrip_Word(); } diff --git a/ctest-next/tests/input/simple.rs b/ctest-next/tests/input/simple.rs index a6f22be4deb39..3374e69287732 100644 --- a/ctest-next/tests/input/simple.rs +++ b/ctest-next/tests/input/simple.rs @@ -4,13 +4,13 @@ pub type Byte = u8; #[repr(C)] pub struct Person { - name: *const c_char, + pub name: *const c_char, age: u8, } #[repr(C)] pub union Word { - word: u16, + pub word: u16, byte: [Byte; 2], } diff --git a/ctest-next/tests/usage.rs b/ctest-next/tests/usage.rs new file mode 100644 index 0000000000000..fb0b4cb48f626 --- /dev/null +++ b/ctest-next/tests/usage.rs @@ -0,0 +1,60 @@ +use std::env; + +use ctest_next::{generate_test, Result, TestGenerator}; + +/// Create a test generator configured to useful settings for this test. +fn default_generator(opt_level: u8, header: &str) -> Result { + env::set_var("OPT_LEVEL", opt_level.to_string()); + let mut generator = TestGenerator::new(); + generator.header(header); + + Ok(generator) +} + +#[test] +fn test_missing_out_dir() { + env::remove_var("OUT_DIR"); + + let mut gen = default_generator(1, "macro.h").unwrap(); + gen.include("tests/input"); + + let result = generate_test(&mut gen, "src/t1.rs", "out_dir_gen.rs"); + assert!(result.is_err(), "Expected error when OUT_DIR is missing"); +} + +#[test] +fn test_invalid_out_dir() { + let mut gen = default_generator(1, "macro.h").unwrap(); + gen.out_dir("/nonexistent_dir").include("tests/input"); + + assert!( + generate_test(&mut gen, "tests/input/macro.rs", "out_path_gen.rs").is_err(), + "Expected error with invalid output path" + ); +} + +#[test] +fn test_non_existent_header() { + let mut gen = default_generator(1, "macro.h").unwrap(); + let out_dir = tempfile::tempdir().unwrap(); + gen.out_dir(out_dir) + .include("tests/input") + .header("nonexistent_header.h"); + + assert!( + generate_test(&mut gen, "tests/input/macro.rs", "missing_header_gen.rs").is_err(), + "Expected error with non-existent header" + ); +} + +#[test] +fn test_invalid_include_path() { + let mut gen = default_generator(1, "macro.h").unwrap(); + let out_dir = tempfile::tempdir().unwrap(); + gen.out_dir(out_dir).include("nonexistent_directory"); + + assert!( + generate_test(&mut gen, "tests/input/macro.rs", "invalid_include_gen.rs").is_err(), + "Expected error with invalid include path" + ); +} diff --git a/ctest-test/Cargo.toml b/ctest-test/Cargo.toml index 64a12e7835040..1edddb3ba1a12 100644 --- a/ctest-test/Cargo.toml +++ b/ctest-test/Cargo.toml @@ -6,14 +6,13 @@ publish = false edition = "2021" [build-dependencies] -ctest = { path = "../ctest" } -cc = "1.2" +ctest-next = { path = "../ctest-next" } +cc = "1.0" [dev-dependencies] -ctest = { path = "../ctest" } +ctest-next = { path = "../ctest-next" } [dependencies] -cfg-if = "1.0.1" libc = { path = ".." } [[bin]] @@ -24,14 +23,6 @@ test = false name = "t2" test = false -[[bin]] -name = "t1_cxx" -test = false - -[[bin]] -name = "t2_cxx" -test = false - # FIXME(msrv): These should be moved to the root Cargo.toml as `[workspace.lints.*]` # once MSRV is above 1.64 and replaced with `[lints] workspace=true` diff --git a/ctest-test/build.rs b/ctest-test/build.rs index b67c2eaaa4639..301f708454035 100644 --- a/ctest-test/build.rs +++ b/ctest-test/build.rs @@ -1,15 +1,20 @@ -use std::process::Command; +use std::env; + +use ctest_next::{generate_test, TestGenerator}; fn main() { - use std::env; let opt_level = env::var("OPT_LEVEL") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(0); + let profile = env::var("PROFILE").unwrap_or_default(); if profile == "release" || opt_level >= 2 { println!("cargo:rustc-cfg=optimized"); } + + // FIXME(ctest): The .c files are ignored right now, I'm not sure if they + // were used or how they were used before. cc::Build::new() .include("src") .warnings(false) @@ -17,91 +22,41 @@ fn main() { .compile("libt1.a"); println!("cargo:rerun-if-changed=src/t1.c"); println!("cargo:rerun-if-changed=src/t1.h"); + cc::Build::new() .warnings(false) .file("src/t2.c") .compile("libt2.a"); println!("cargo:rerun-if-changed=src/t2.c"); println!("cargo:rerun-if-changed=src/t2.h"); - ctest::TestGenerator::new() + + let mut t1gen = TestGenerator::new(); + t1gen .header("t1.h") .include("src") - .fn_cname(|a, b| b.unwrap_or(a).to_string()) - .type_name(move |ty, is_struct, is_union| match ty { - "T1Union" => ty.to_string(), - "Transparent" => ty.to_string(), - t if is_struct => format!("struct {t}"), - t if is_union => format!("union {t}"), - t => t.to_string(), - }) - .volatile_item(t1_volatile) - .array_arg(t1_arrays) - .skip_roundtrip(|n| n == "Arr") - .generate("src/t1.rs", "t1gen.rs"); - ctest::TestGenerator::new() + .skip_private(true) + .rename_fn(|f| f.link_name().unwrap_or(f.ident()).to_string().into()) + .rename_union_ty(|ty| (ty == "T1Union").then_some(ty.to_string())) + .rename_struct_ty(|ty| (ty == "Transparent").then_some(ty.to_string())) + .volatile_field(|s, f| s.ident() == "V" && f.ident() == "v") + .volatile_static(|s| s.ident() == "vol_ptr") + .volatile_static(|s| s.ident() == "T1_fn_ptr_vol") + .volatile_fn_arg(|f, p| f.ident() == "T1_vol0" && p.ident() == "arg0") + .volatile_fn_arg(|f, p| f.ident() == "T1_vol2" && p.ident() == "arg1") + .volatile_fn_return_type(|f| f.ident() == "T1_vol1") + .volatile_fn_return_type(|f| f.ident() == "T1_vol2") + // The parameter `a` of the functions `T1r`, `T1s`, `T1t`, `T1v` is an array. + .array_arg(|f, p| matches!(f.ident(), "T1r" | "T1s" | "T1t" | "T1v") && p.ident() == "a") + .skip_roundtrip(|n| n == "Arr"); + generate_test(&mut t1gen, "src/t1.rs", "t1gen.rs").unwrap(); + + let mut t2gen = TestGenerator::new(); + t2gen .header("t2.h") .include("src") - .type_name(move |ty, is_struct, is_union| match ty { - "T2Union" => ty.to_string(), - t if is_struct => format!("struct {t}"), - t if is_union => format!("union {t}"), - t => t.to_string(), - }) - .skip_roundtrip(|_| true) - .generate("src/t2.rs", "t2gen.rs"); - - println!("cargo::rustc-check-cfg=cfg(has_cxx)"); - if !cfg!(unix) || Command::new("c++").arg("v").output().is_ok() { - // A C compiler is always available, but these are only run if a C++ compiler is - // also available. - println!("cargo::rustc-cfg=has_cxx"); - - ctest::TestGenerator::new() - .header("t1.h") - .language(ctest::Lang::CXX) - .include("src") - .fn_cname(|a, b| b.unwrap_or(a).to_string()) - .type_name(move |ty, is_struct, is_union| match ty { - "T1Union" => ty.to_string(), - "Transparent" => ty.to_string(), - t if is_struct => format!("struct {t}"), - t if is_union => format!("union {t}"), - t => t.to_string(), - }) - .volatile_item(t1_volatile) - .array_arg(t1_arrays) - .skip_roundtrip(|n| n == "Arr") - .generate("src/t1.rs", "t1gen_cxx.rs"); - ctest::TestGenerator::new() - .header("t2.h") - .language(ctest::Lang::CXX) - .include("src") - .type_name(move |ty, is_struct, is_union| match ty { - "T2Union" => ty.to_string(), - t if is_struct => format!("struct {t}"), - t if is_union => format!("union {t}"), - t => t.to_string(), - }) - .skip_roundtrip(|_| true) - .generate("src/t2.rs", "t2gen_cxx.rs"); - } else { - println!("cargo::warning=skipping C++ tests"); - } -} - -fn t1_volatile(i: ctest::VolatileItemKind) -> bool { - use ctest::VolatileItemKind::*; - match i { - StructField(ref n, ref f) if n == "V" && f == "v" => true, - Static(ref n) if n == "vol_ptr" => true, - FunctionArg(ref n, 0) if n == "T1_vol0" => true, - FunctionArg(ref n, 1) if n == "T1_vol2" => true, - FunctionRet(ref n) if n == "T1_vol1" || n == "T1_vol2" => true, - Static(ref n) if n == "T1_fn_ptr_vol" => true, - _ => false, - } -} - -fn t1_arrays(n: &str, i: usize) -> bool { - i == 0 && matches!(n, "T1r" | "T1s" | "T1t" | "T1v") + // public C typedefs have to manually be specified because they are identical to normal + // structs on the Rust side. + .rename_union_ty(|ty| (ty == "T2Union").then_some(ty.to_string())) + .skip_roundtrip(|_| true); + generate_test(&mut t2gen, "src/t2.rs", "t2gen.rs").unwrap(); } diff --git a/ctest-test/src/bin/t1.rs b/ctest-test/src/bin/t1.rs index cbe9090eecd4b..a788bf924c918 100644 --- a/ctest-test/src/bin/t1.rs +++ b/ctest-test/src/bin/t1.rs @@ -2,6 +2,5 @@ #![deny(warnings)] use ctest_test::t1::*; -use libc::*; include!(concat!(env!("OUT_DIR"), "/t1gen.rs")); diff --git a/ctest-test/src/bin/t1_cxx.rs b/ctest-test/src/bin/t1_cxx.rs deleted file mode 100644 index 2e1e192a1e210..0000000000000 --- a/ctest-test/src/bin/t1_cxx.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![cfg(not(test))] - -cfg_if::cfg_if! { - if #[cfg(has_cxx)] { - use ctest_test::t1::*; - use libc::*; - - include!(concat!(env!("OUT_DIR"), "/t1gen_cxx.rs")); - } else { - fn main() {} - } -} diff --git a/ctest-test/src/bin/t2_cxx.rs b/ctest-test/src/bin/t2_cxx.rs deleted file mode 100644 index 7ef46bb6a004a..0000000000000 --- a/ctest-test/src/bin/t2_cxx.rs +++ /dev/null @@ -1,11 +0,0 @@ -#![cfg(not(test))] - -cfg_if::cfg_if! { - if #[cfg(has_cxx)] { - use ctest_test::t2::*; - - include!(concat!(env!("OUT_DIR"), "/t2gen_cxx.rs")); - } else { - fn main() {} - } -} diff --git a/ctest-test/src/lib.rs b/ctest-test/src/lib.rs index d54b4ede501b1..8bb86699f30c7 100644 --- a/ctest-test/src/lib.rs +++ b/ctest-test/src/lib.rs @@ -1,5 +1,6 @@ -// src/** is mostly dummy files -#![allow(clippy::style, clippy::correctness)] +//! `ctest-next-test` is a test crate for testing `ctest-next`. It consists +//! of two test binaries, t1 and t2 that test various aspects of `ctest-next`, +//! such as validation and generation of tests. pub mod t1; pub mod t2; diff --git a/ctest-test/src/t1.c b/ctest-test/src/t1.c index 81cd7d7915cd1..24f9fe52bf215 100644 --- a/ctest-test/src/t1.c +++ b/ctest-test/src/t1.c @@ -1,15 +1,15 @@ -#include -#include #include "t1.h" +#include +#include void T1a(void) {} -void* T1b(void) { return NULL; } -void* T1c(void* a) { return NULL; } -int32_t T1d(unsigned a ) { return 0; } -void T1e(unsigned a, const struct T1Bar* b) { } +void *T1b(void) { return NULL; } +void *T1c(void *a) { return NULL; } +int32_t T1d(unsigned a) { return 0; } +void T1e(unsigned a, const struct T1Bar *b) {} void T1f(void) {} -void T1g(int32_t* a) {} -void T1h(const int32_t* b) {} +void T1g(int32_t *a) {} +void T1h(const int32_t *b) {} void T1i(int32_t a[4]) {} void T1j(const int32_t b[4]) {} void T1o(int32_t (*a)[4]) {} @@ -17,8 +17,8 @@ void T1p(int32_t (*const a)[4]) {} void T1r(Arr a) {} void T1s(const Arr a) {} -void T1t(Arr* a) {} -void T1v(const Arr* a) {} +void T1t(Arr *a) {} +void T1v(const Arr *a) {} unsigned T1static = 3; @@ -29,11 +29,13 @@ uint8_t foo(uint8_t a, uint8_t b) { return a + b; } void bar(uint8_t a) { return; } void baz(void) { return; } -uint32_t (*nested(uint8_t arg))(uint16_t) { +uint32_t (*nested(uint8_t arg))(uint16_t) +{ return NULL; } -uint32_t (*nested2(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) { +uint32_t (*nested2(uint8_t (*arg0)(uint8_t), uint16_t (*arg1)(uint16_t)))(uint16_t) +{ return NULL; } @@ -46,7 +48,7 @@ const uint8_t T1_static_right = 7; uint8_t (*T1_static_right2)(uint8_t, uint8_t) = foo; uint32_t (*(*T1_fn_ptr_s)(uint8_t))(uint16_t) = nested; -uint32_t (*(*T1_fn_ptr_s2)(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) = nested2; +uint32_t (*(*T1_fn_ptr_s2)(uint8_t (*arg0)(uint8_t), uint16_t (*arg1)(uint16_t)))(uint16_t) = nested2; const int32_t T1_arr0[2] = {0, 0}; const int32_t T1_arr1[2][3] = {{0, 0, 0}, {0, 0, 0}}; @@ -57,21 +59,12 @@ int32_t T1_arr4[2][3] = {{0, 0, 0}, {0, 0, 0}}; int32_t T1_arr5[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; int32_t T1_arr42[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; -const int16_t* T1_sref = (void*)(1337); - -const int32_t* T1_mut_opt_ref = NULL; -int32_t* T1_mut_opt_mut_ref = NULL; -const int32_t* T1_const_opt_const_ref = NULL; - -void (*const T1_opt_fn1)(void) = baz; -uint32_t (*(*T1_opt_fn2)(uint8_t))(uint16_t) = nested; -uint32_t (*(*T1_opt_fn3)(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) = nested2; +const int16_t *T1_sref = (void *)(1337); -volatile uint8_t* vol_ptr = NULL; -void* T1_vol0(volatile void* x, void* a) { return a? a: (void*)x; } -volatile void* T1_vol1(void* x, void* b) { return b? (volatile void*)x : (volatile void*)x; } -volatile void* T1_vol2(void* c, volatile void* x) { return c? x : x; } +volatile uint8_t *vol_ptr = NULL; +void *T1_vol0(volatile void *x, void *a) { return a ? a : (void *)x; } +volatile void *T1_vol1(void *x, void *b) { return b ? (volatile void *)x : (volatile void *)x; } +volatile void *T1_vol2(void *c, volatile void *x) { return c ? x : x; } -/* FIXME(#4365): duplicate symbol errors when enabled -uint8_t (* volatile T1_fn_ptr_vol)(uint8_t, uint8_t) = foo; -*/ +// FIXME(#4365): duplicate symbol errors when enabled +// uint8_t (*volatile T1_fn_ptr_vol)(uint8_t, uint8_t) = foo; diff --git a/ctest-test/src/t1.cpp b/ctest-test/src/t1.cpp deleted file mode 120000 index 1627f65e030cd..0000000000000 --- a/ctest-test/src/t1.cpp +++ /dev/null @@ -1 +0,0 @@ -t1.c \ No newline at end of file diff --git a/ctest-test/src/t1.h b/ctest-test/src/t1.h index e610bb10d053a..08800525651d7 100644 --- a/ctest-test/src/t1.h +++ b/ctest-test/src/t1.h @@ -5,7 +5,8 @@ typedef int32_t T1Foo; #define T1N 5 #define T1S "foo" -struct T1Bar { +struct T1Bar +{ int32_t a; uint32_t b; T1Foo c; @@ -14,49 +15,53 @@ struct T1Bar { int64_t f[T1N][2]; }; -struct T1Baz { +struct T1Baz +{ uint64_t a; struct T1Bar b; }; -typedef union { +typedef union +{ uint64_t a; uint32_t b; } T1Union; -union T1NoTypedefUnion { - uint64_t a; - uint32_t b; +union T1NoTypedefUnion +{ + uint64_t a; + uint32_t b; }; -struct T1StructWithUnion { - union T1NoTypedefUnion u; +struct T1StructWithUnion +{ + union T1NoTypedefUnion u; }; typedef double T1TypedefDouble; -typedef int* T1TypedefPtr; +typedef int *T1TypedefPtr; typedef struct T1Bar T1TypedefStruct; void T1a(void); -void* T1b(void); -void* T1c(void*); +void *T1b(void); +void *T1c(void *); int32_t T1d(unsigned); -void T1e(unsigned, const struct T1Bar*); +void T1e(unsigned, const struct T1Bar *); void T1f(void); -void T1g(int32_t* a); -void T1h(const int32_t* b); +void T1g(int32_t *a); +void T1h(const int32_t *b); void T1i(int32_t a[4]); void T1j(const int32_t b[4]); void T1o(int32_t (*a)[4]); void T1p(int32_t (*const a)[4]); -typedef int32_t (Arr)[4]; +typedef int32_t(Arr)[4]; typedef int32_t Transparent; void T1r(Arr a); void T1s(const Arr a); -void T1t(Arr* a); -void T1v(const Arr* a); +void T1t(Arr *a); +void T1v(const Arr *a); #define T1C 4 @@ -95,31 +100,22 @@ extern int32_t T1_arr5[1][2][3]; extern int32_t T1_arr42[1][2][3]; -extern const int16_t* T1_sref; - -extern const int32_t* T1_mut_opt_ref; -extern int32_t* T1_mut_opt_mut_ref; -extern const int32_t* T1_const_opt_const_ref; - -extern void (*const T1_opt_fn1)(void); -/* FIXME(#4365): duplicate symbol errors when enabled -// uint32_t (*(*T1_opt_fn2)(uint8_t))(uint16_t); -// uint32_t (*(*T1_opt_fn3)(uint8_t(*)(uint8_t), uint16_t(*)(uint16_t)))(uint16_t); -*/ +extern const int16_t *T1_sref; - -struct Q { - uint8_t* q0; - uint8_t** q1; +struct Q +{ + uint8_t *q0; + uint8_t **q1; uint8_t q2; }; - -struct T1_conflict_foo { +struct T1_conflict_foo +{ int a; }; -struct T1_conflict{ +struct T1_conflict +{ int foo; }; @@ -128,36 +124,39 @@ struct T1_conflict{ // on msvc there is only pragma pack // on clang and gcc there is a packed attribute -# pragma pack(push,1) +#pragma pack(push, 1) -struct Pack { +struct Pack +{ uint8_t a; uint16_t b; }; -# pragma pack(pop) +#pragma pack(pop) -# pragma pack(push,4) +#pragma pack(push, 4) -struct Pack4 { +struct Pack4 +{ uint8_t a; uint32_t b; }; -# pragma pack(pop) +#pragma pack(pop) // volatile pointers in struct fields: -struct V { - volatile uint8_t* v; +struct V +{ + volatile uint8_t *v; }; // volatile pointers in externs: -extern volatile uint8_t* vol_ptr; +extern volatile uint8_t *vol_ptr; // volatile pointers in function arguments: -void* T1_vol0(volatile void*, void*); -volatile void* T1_vol1(void*, void*); -volatile void* T1_vol2(void*, volatile void*); +void *T1_vol0(volatile void *, void *); +volatile void *T1_vol1(void *, void *); +volatile void *T1_vol2(void *, volatile void *); /* FIXME(#4365): duplicate symbol errors when enabled // volatile function pointers: @@ -166,7 +165,8 @@ uint8_t (*volatile T1_fn_ptr_vol)(uint8_t, uint8_t); #define LOG_MAX_LINE_LENGTH (1400) -typedef struct { +typedef struct +{ long tv_sec; int tv_usec; } timeval; diff --git a/ctest-test/src/t1.rs b/ctest-test/src/t1.rs index 77a2873204c5f..9d8f5bbb30172 100644 --- a/ctest-test/src/t1.rs +++ b/ctest-test/src/t1.rs @@ -1,9 +1,9 @@ -#![allow(dead_code)] +#![allow(non_camel_case_types)] -use libc::*; +use std::ffi::{c_char, c_double, c_int, c_long, c_uint, c_void}; pub type T1Foo = i32; -pub const T1S: &str = "foo"; +pub const T1S: *const c_char = c"foo".as_ptr(); pub const T1N: i32 = 5; @@ -57,6 +57,7 @@ i! { pub const T1C: u32 = 4; } +#[expect(unused)] const NOT_PRESENT: u32 = 5; pub type Arr = [i32; 4]; @@ -88,7 +89,8 @@ extern "C" { } pub fn foo() { - assert_eq!(1, 1); + let x = 1; + assert_eq!(x, 1); } extern "C" { @@ -126,21 +128,6 @@ extern "C" { pub static mut T1_arr6: [[[i32; 3]; 2]; 1]; pub static mut T1_sref: &'static i16; - - pub static mut T1_mut_opt_ref: Option<&'static i32>; - pub static mut T1_mut_opt_mut_ref: Option<&'static mut i32>; - pub static T1_const_opt_const_ref: Option<&'static i32>; - - pub static T1_opt_fn1: Option ()>; - /* FIXME(#4365): duplicate symbol errors when enabled - pub static T1_opt_fn2: Option extern "C" fn(u16) -> u32>; - pub static T1_opt_fn3: Option< - unsafe extern "C" fn( - extern "C" fn(u8) -> u8, - extern "C" fn(u16) -> u16, - ) -> extern "C" fn(u16) -> u32, - >; - */ } #[repr(C)] @@ -182,9 +169,6 @@ extern "C" { pub fn T1_vol0(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; pub fn T1_vol1(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; pub fn T1_vol2(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; - /* FIXME(#4365): duplicate symbol errors when enabled - pub static T1_fn_ptr_vol: Option u8>; - */ } pub const LOG_MAX_LINE_LENGTH: usize = 1400; @@ -195,6 +179,7 @@ struct timeval { tv_usec: c_int, } +#[expect(unused)] #[repr(C)] struct log_record_t { level: c_long, @@ -205,11 +190,14 @@ struct log_record_t { message: [c_char; LOG_MAX_LINE_LENGTH], } +#[expect(unused)] #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] #[repr(C, align(16))] struct LongDoubleWrap { inner: u128, } + +#[expect(unused)] #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] #[repr(C)] struct LongDoubleWrap { diff --git a/ctest-test/src/t2.cpp b/ctest-test/src/t2.cpp deleted file mode 120000 index 02be41dad0810..0000000000000 --- a/ctest-test/src/t2.cpp +++ /dev/null @@ -1 +0,0 @@ -t2.c \ No newline at end of file diff --git a/ctest-test/src/t2.h b/ctest-test/src/t2.h index 9f99e11a1e79d..9fd4fc3b5aa6d 100644 --- a/ctest-test/src/t2.h +++ b/ctest-test/src/t2.h @@ -6,18 +6,21 @@ typedef int8_t T2Bar; typedef T2Foo T2TypedefFoo; typedef unsigned T2TypedefInt; -struct T2Baz { +struct T2Baz +{ int8_t _a; int64_t a; uint32_t b; }; -typedef struct { +typedef struct +{ uint32_t a; int64_t b; } T2Union; -static void T2a(void) {} +// FIXME(ctest): Will fail as unused function until extern functions are tested. +// static void T2a(void) {} #define T2C 4 #define T2S "a" diff --git a/ctest-test/src/t2.rs b/ctest-test/src/t2.rs index bafeaef7cd897..cfd3bfb65b418 100644 --- a/ctest-test/src/t2.rs +++ b/ctest-test/src/t2.rs @@ -1,4 +1,4 @@ -use libc::*; +use std::ffi::{c_char, c_int}; pub type T2Foo = u32; pub type T2Bar = u32; @@ -28,9 +28,10 @@ pub union T2Union { pub const T2C: i32 = 5; i! { - pub const T2S: &str = "b"; + pub const T2S: *const c_char = c"b".as_ptr(); } -extern "C" { - pub fn T2a(); -} +// FIXME(ctest): Will fail as unused function until extern functions are tested. +// extern "C" { +// pub fn T2a(); +// } diff --git a/ctest-test/tests/all.rs b/ctest-test/tests/all.rs index b8f29e6799737..327693a53d485 100644 --- a/ctest-test/tests/all.rs +++ b/ctest-test/tests/all.rs @@ -5,16 +5,18 @@ use std::collections::HashSet; use std::env; use std::process::{Command, ExitStatus}; +/// Create a command that starts in the `target/debug` or `target/release` directory. fn cmd(name: &str) -> Command { - let mut p = env::current_exe().unwrap(); - p.pop(); - if p.file_name().unwrap().to_str() == Some("deps") { - p.pop(); + let mut path = env::current_exe().unwrap(); + path.pop(); + if path.file_name().unwrap().to_str() == Some("deps") { + path.pop(); } - p.push(name); - Command::new(p) + path.push(name); + Command::new(path) } +/// Executes a command, returning stdout and stderr combined and it's status. fn output(cmd: &mut Command) -> (String, ExitStatus) { eprintln!("command: {cmd:?}"); let output = cmd.output().unwrap(); @@ -25,25 +27,21 @@ fn output(cmd: &mut Command) -> (String, ExitStatus) { } #[test] -fn t1() { - let (o, status) = output(&mut cmd("t1")); - assert!(status.success(), "output: {o}"); - assert!(!o.contains("bad "), "{o}"); - eprintln!("o: {o}"); +fn t1_next() { + // t1 must run to completion without any errors. + let (output, status) = output(&mut cmd("t1-next")); + assert!(status.success(), "output: {output}"); + assert!(!output.contains("bad "), "{output}"); + eprintln!("output: {output}"); } #[test] -#[cfg(has_cxx)] -fn t1_cxx() { - let (o, status) = output(&mut cmd("t1_cxx")); - assert!(status.success(), "output: {o}"); - assert!(!o.contains("bad "), "{o}"); -} +fn t2_next() { + // t2 must fail to run to completion, and only have the errors we expect it to have. + let (output, status) = output(&mut cmd("t2-next")); + assert!(!status.success(), "output: {output}"); -#[test] -fn t2() { - let (o, status) = output(&mut cmd("t2")); - assert!(!status.success(), "output: {o}"); + // FIXME(ctest): Errors currently commented out are not tested. let errors = [ "bad T2Foo signed", "bad T2TypedefFoo signed", @@ -56,7 +54,7 @@ fn t2() { "bad field type a of T2Baz", "bad field offset b of T2Baz", "bad field type b of T2Baz", - "bad T2a function pointer", + // "bad T2a function pointer", "bad T2C value at byte 0", "bad T2S string", "bad T2Union size", @@ -65,137 +63,24 @@ fn t2() { ]; let mut errors = errors.iter().cloned().collect::>(); + // Extract any errors that are not contained within the known error set. let mut bad = false; - for line in o.lines().filter(|l| l.starts_with("bad ")) { + for line in output.lines().filter(|l| l.starts_with("bad ")) { let msg = &line[..line.find(":").unwrap()]; if !errors.remove(&msg) { - println!("unknown error: {msg}"); + println!("unknown error: {msg:#?}"); bad = true; } } + // If any errors are left over, t2 did not run properly. for error in errors { println!("didn't find error: {error}"); bad = true; } - if bad { - println!("output was:\n\n{o}"); - panic!(); - } -} - -#[test] -#[cfg(has_cxx)] -fn t2_cxx() { - let (o, status) = output(&mut cmd("t2_cxx")); - assert!(!status.success(), "output: {o}"); - let errors = [ - "bad T2Foo signed", - "bad T2TypedefFoo signed", - "bad T2TypedefInt signed", - "bad T2Bar size", - "bad T2Bar align", - "bad T2Bar signed", - "bad T2Baz size", - "bad field offset a of T2Baz", - "bad field type a of T2Baz", - "bad field offset b of T2Baz", - "bad field type b of T2Baz", - "bad T2a function pointer", - "bad T2C value at byte 0", - "bad T2S string", - "bad T2Union size", - "bad field type b of T2Union", - "bad field offset b of T2Union", - ]; - let mut errors = errors.iter().cloned().collect::>(); - - let mut bad = false; - for line in o.lines().filter(|l| l.starts_with("bad ")) { - let msg = &line[..line.find(":").unwrap()]; - if !errors.remove(&msg) { - println!("unknown error: {msg}"); - bad = true; - } - } - for error in errors { - println!("didn't find error: {error}"); - bad = true; - } if bad { - println!("output was:\n\n{o}"); + println!("output was:\n\n{output:#?}"); panic!(); } } - -#[test] -fn test_missing_out_dir() { - // Save original OUT_DIR - let orig_out_dir = env::var_os("OUT_DIR"); - env::remove_var("OUT_DIR"); - - // Test error handling for OUT_DIR missing - let result = ctest::TestGenerator::new() - .header("t1.h") - .try_generate("src/t1.rs", "out_dir_gen.rs"); - - // Restore OUT_DIR - if let Some(dir) = orig_out_dir { - env::set_var("OUT_DIR", dir); - } - - assert!(result.is_err(), "Expected error when OUT_DIR is missing"); -} - -#[test] -fn test_invalid_output_path() { - // Test error handling for invalid output path - let err = ctest::TestGenerator::new() - .header("t1.h") - .include("src") - .out_dir("/nonexistent_dir") // Should fail with permission error - .try_generate("src/t1.rs", "out_path_gen.rs"); - - assert!(err.is_err(), "Expected error with invalid output path"); -} - -#[test] -fn test_parsing_error() { - // Test parsing error - // Create a temporary file with invalid Rust syntax - let temp_dir = env::temp_dir(); - let invalid_file = temp_dir.join("invalid.rs"); - std::fs::write(&invalid_file, "fn invalid_syntax {").unwrap(); - - let err = ctest::TestGenerator::new() - .header("t1.h") - .include("src") - .target("x86_64-unknown-linux-gnu") - .try_generate(&invalid_file, "parse_gen.rs"); - - assert!(err.is_err(), "Expected error when parsing invalid syntax"); - let _ = std::fs::remove_file(invalid_file); -} - -#[test] -fn test_non_existent_header() { - // Test non-existent header - let err = ctest::TestGenerator::new() - .header("nonexistent_header.h") - .include("src") - .try_generate("src/t1.rs", "missing_header_gen.rs"); - - assert!(err.is_err(), "Expected error with non-existent header"); -} - -#[test] -fn test_invalid_include_path() { - // Test invalid include path - let err = ctest::TestGenerator::new() - .header("t1.h") - .include("nonexistent_directory") - .try_generate("src/t1.rs", "invalid_include_gen.rs"); - - assert!(err.is_err(), "Expected error with invalid include path"); -}