diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 201f9294a141a..c95e3fb96a9a3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -89,7 +89,7 @@ jobs: # Remove `-Dwarnings` at the MSRV since lints may be different export RUSTFLAGS="" # Remove `ctest-next` which uses the 2024 edition - perl -i -ne 'print unless /"ctest-(next|test)",/' Cargo.toml + perl -i -ne 'print unless /"ctest-(next|test)",/ || /"libc-test",/' Cargo.toml fi ./ci/verify-build.sh @@ -320,7 +320,7 @@ jobs: - name: Install Rust run: rustup update "$MSRV" --no-self-update && rustup default "$MSRV" - name: Remove edition 2024 crates - run: perl -i -ne 'print unless /"ctest-(next|test)",/' Cargo.toml + run: perl -i -ne 'print unless /"ctest-(next|test)",/ || /"libc-test",/' Cargo.toml - uses: Swatinem/rust-cache@v2 - run: cargo build -p ctest diff --git a/Cargo.lock b/Cargo.lock index 33858c766027e..b7f951a0e178b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,7 @@ dependencies = [ "cc", "cfg-if 1.0.1", "ctest", + "ctest-next", "glob", "libc 1.0.0-alpha.1", "proc-macro2", diff --git a/ctest-next/src/ast/union.rs b/ctest-next/src/ast/union.rs index bdbfc9b162c03..12eedf7e002a3 100644 --- a/ctest-next/src/ast/union.rs +++ b/ctest-next/src/ast/union.rs @@ -3,7 +3,6 @@ use crate::{BoxStr, Field}; /// Represents a union defined in Rust. #[derive(Debug, Clone)] pub struct Union { - #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, pub(crate) fields: Vec, diff --git a/ctest-next/src/generator.rs b/ctest-next/src/generator.rs index 89d45bcc99e26..36db70ffd1b7e 100644 --- a/ctest-next/src/generator.rs +++ b/ctest-next/src/generator.rs @@ -40,10 +40,10 @@ pub struct TestGenerator { cfg: Vec<(String, Option)>, mapped_names: Vec, pub(crate) skips: Vec, - verbose_skip: bool, + pub(crate) verbose_skip: bool, pub(crate) volatile_items: Vec, pub(crate) array_arg: Option, - skip_private: bool, + pub(crate) skip_private: bool, pub(crate) skip_roundtrip: Option, pub(crate) skip_signededness: Option, pub(crate) skip_fn_ptrcheck: Option, @@ -898,8 +898,6 @@ impl TestGenerator { let mut ffi_items = FfiItems::new(); ffi_items.visit_file(&ast); - self.filter_ffi_items(&mut ffi_items); - let output_directory = self .out_dir .clone() @@ -938,37 +936,6 @@ impl TestGenerator { Ok(output_file_path) } - /// Skips entire items such as structs, constants, and aliases from being tested. - /// - /// 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; - - macro_rules! filter { - ($field:ident, $variant:ident, $label:literal) => {{ - let skipped: Vec<_> = ffi_items - .$field - .extract_if(.., |item| { - self.skips.iter().any(|f| f(&MapInput::$variant(item))) - || (self.skip_private && !item.public) - }) - .collect(); - if verbose { - skipped - .iter() - .for_each(|item| eprintln!("Skipping {} \"{}\"", $label, item.ident())); - } - }}; - } - - filter!(aliases, Alias, "alias"); - filter!(constants, Const, "const"); - filter!(structs, Struct, "struct"); - filter!(foreign_functions, Fn, "fn"); - filter!(foreign_statics, Static, "static"); - } - /// Maps Rust identifiers or types to C counterparts, or defaults to the original name. pub(crate) fn rty_to_cty<'a>(&self, item: impl Into>) -> String { let item = item.into(); diff --git a/ctest-next/src/template.rs b/ctest-next/src/template.rs index 41091b3872bb2..b1b47fd1e4cf6 100644 --- a/ctest-next/src/template.rs +++ b/ctest-next/src/template.rs @@ -3,6 +3,7 @@ use proc_macro2::Span; use quote::ToTokens; use syn::spanned::Spanned; +use crate::cdecl::Constness; use crate::ffi_items::FfiItems; use crate::translator::{TranslationErrorKind, Translator}; use crate::{ @@ -86,7 +87,7 @@ impl TestTemplate { &mut self, helper: &TranslateHelper, ) -> Result<(), TranslationError> { - for constant in helper.ffi_items.constants() { + for constant in helper.filtered_ffi_items.constants() { if let syn::Type::Ptr(ptr) = &constant.ty && let syn::Type::Path(path) = &*ptr.elem && path.path.segments.last().unwrap().ident == "c_char" @@ -124,7 +125,7 @@ impl TestTemplate { &mut self, helper: &TranslateHelper, ) -> Result<(), TranslationError> { - for alias in helper.ffi_items.aliases() { + for alias in helper.filtered_ffi_items.aliases() { let item = TestSizeAlign { test_name: size_align_test_ident(alias.ident()), id: alias.ident().into(), @@ -134,7 +135,7 @@ impl TestTemplate { self.size_align_tests.push(item.clone()); self.test_idents.push(item.test_name); } - for struct_ in helper.ffi_items.structs() { + for struct_ in helper.filtered_ffi_items.structs() { let item = TestSizeAlign { test_name: size_align_test_ident(struct_.ident()), id: struct_.ident().into(), @@ -144,7 +145,7 @@ impl TestTemplate { self.size_align_tests.push(item.clone()); self.test_idents.push(item.test_name); } - for union_ in helper.ffi_items.unions() { + for union_ in helper.filtered_ffi_items.unions() { let item = TestSizeAlign { test_name: size_align_test_ident(union_.ident()), id: union_.ident().into(), @@ -165,7 +166,7 @@ impl TestTemplate { &mut self, helper: &TranslateHelper, ) -> Result<(), TranslationError> { - for alias in helper.ffi_items.aliases() { + for alias in helper.filtered_ffi_items.aliases() { let should_skip_signededness_test = helper .generator .skip_signededness @@ -197,7 +198,7 @@ impl TestTemplate { let should_skip = |map_input| helper.generator.skips.iter().any(|f| f(&map_input)); let struct_fields = helper - .ffi_items + .filtered_ffi_items .structs() .iter() .flat_map(|struct_| struct_.fields.iter().map(move |field| (struct_, field))) @@ -213,7 +214,7 @@ impl TestTemplate { ) }); let union_fields = helper - .ffi_items + .filtered_ffi_items .unions() .iter() .flat_map(|union_| union_.fields.iter().map(move |field| (union_, field))) @@ -251,15 +252,15 @@ impl TestTemplate { &mut self, helper: &TranslateHelper, ) -> Result<(), TranslationError> { - for alias in helper.ffi_items.aliases() { + for alias in helper.filtered_ffi_items.aliases() { let c_ty = helper.c_type(alias)?; self.add_roundtrip_test(helper, alias.ident(), &[], &c_ty, true); } - for struct_ in helper.ffi_items.structs() { + for struct_ in helper.filtered_ffi_items.structs() { let c_ty = helper.c_type(struct_)?; self.add_roundtrip_test(helper, struct_.ident(), &struct_.fields, &c_ty, false); } - for union_ in helper.ffi_items.unions() { + for union_ in helper.filtered_ffi_items.unions() { let c_ty = helper.c_type(union_)?; self.add_roundtrip_test(helper, union_.ident(), &union_.fields, &c_ty, false); } @@ -303,14 +304,14 @@ impl TestTemplate { let should_skip = |map_input| helper.generator.skips.iter().any(|f| f(&map_input)); let struct_fields = helper - .ffi_items + .filtered_ffi_items .structs() .iter() .flat_map(|s| s.fields.iter().map(move |f| (s, f))) .filter(|(s, f)| { - !should_skip(MapInput::StructField(s, f)) - && !should_skip(MapInput::StructFieldType(s, f)) - && f.public + !(should_skip(MapInput::StructField(s, f)) + || should_skip(MapInput::StructFieldType(s, f)) + || !f.public) }) .map(|(s, f)| { ( @@ -332,14 +333,14 @@ impl TestTemplate { ) }); let union_fields = helper - .ffi_items + .filtered_ffi_items .unions() .iter() .flat_map(|u| u.fields.iter().map(move |f| (u, f))) .filter(|(u, f)| { - !should_skip(MapInput::UnionField(u, f)) - && !should_skip(MapInput::UnionFieldType(u, f)) - && f.public + !(should_skip(MapInput::UnionField(u, f)) + || should_skip(MapInput::UnionFieldType(u, f)) + || !f.public) }) .map(|(u, f)| { ( @@ -532,6 +533,7 @@ fn foreign_fn_test_ident(ident: &str) -> BoxStr { /// Wrap methods that depend on both ffi items and the generator. pub(crate) struct TranslateHelper<'a> { + filtered_ffi_items: FfiItems, ffi_items: &'a FfiItems, generator: &'a TestGenerator, translator: Translator<'a>, @@ -540,11 +542,49 @@ pub(crate) struct TranslateHelper<'a> { impl<'a> TranslateHelper<'a> { /// Create a new translation helper. pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self { - Self { + let filtered_ffi_items = ffi_items.clone(); + let mut helper = Self { + filtered_ffi_items, ffi_items, generator, translator: Translator::new(ffi_items, generator), + }; + helper.filter_ffi_items(); + + helper + } + + /// Skips entire items such as structs, constants, and aliases from being tested. + /// + /// 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(&mut self) { + let verbose = self.generator.verbose_skip; + + macro_rules! filter { + ($field:ident, $variant:ident, $label:literal) => {{ + let skipped = self.filtered_ffi_items.$field.extract_if(.., |item| { + (self.generator.skip_private && !item.public) + || self + .generator + .skips + .iter() + .any(|f| f(&MapInput::$variant(item))) + }); + for item in skipped { + if verbose { + eprintln!("Skipping {} \"{}\"", $label, item.ident()) + } + } + }}; } + + filter!(aliases, Alias, "alias"); + filter!(constants, Const, "const"); + filter!(structs, Struct, "struct"); + filter!(unions, Union, "union"); + filter!(foreign_functions, Fn, "fn"); + filter!(foreign_statics, Static, "static"); } /// Returns the equivalent C/Cpp identifier of the Rust item. @@ -565,9 +605,9 @@ impl<'a> TranslateHelper<'a> { // inside of `Fn` when parsed. MapInput::Fn(_) => unimplemented!(), // For structs/unions/aliases, their type is the same as their identifier. - MapInput::Alias(a) => (a.ident(), cdecl::named(a.ident(), cdecl::Constness::Mut)), - MapInput::Struct(s) => (s.ident(), cdecl::named(s.ident(), cdecl::Constness::Mut)), - MapInput::Union(u) => (u.ident(), cdecl::named(u.ident(), cdecl::Constness::Mut)), + MapInput::Alias(a) => (a.ident(), cdecl::named(a.ident(), Constness::Mut)), + MapInput::Struct(s) => (s.ident(), cdecl::named(s.ident(), Constness::Mut)), + MapInput::Union(u) => (u.ident(), cdecl::named(u.ident(), Constness::Mut)), MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"), MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"), @@ -584,7 +624,7 @@ impl<'a> TranslateHelper<'a> { ) })?; - let item = if self.ffi_items.contains_struct(ident) { + let item = if self.ffi_items.contains_struct(&ty) { MapInput::StructType(&ty) } else if self.ffi_items.contains_union(ident) { MapInput::UnionType(&ty) diff --git a/ctest-next/src/tests.rs b/ctest-next/src/tests.rs index 0df50a59c03a7..5905aa7bdff7c 100644 --- a/ctest-next/src/tests.rs +++ b/ctest-next/src/tests.rs @@ -127,3 +127,11 @@ fn test_translate_helper_array_1d_2d() { assert_r2cdecl("[u8; 10]", "uint8_t foo[10]"); assert_r2cdecl("[[u8; 64]; 32]", "uint8_t foo[32][64]"); } + +#[test] +fn test_translate_expr_literal_types() { + assert_eq!( + r2cdecl("[u8; 10usize]", "foo").unwrap(), + "uint8_t foo[(size_t)10]" + ); +} diff --git a/ctest-next/src/translator.rs b/ctest-next/src/translator.rs index 027fa1a2cf3b4..23c58631bbde6 100644 --- a/ctest-next/src/translator.rs +++ b/ctest-next/src/translator.rs @@ -364,6 +364,24 @@ pub(crate) fn ptr_with_inner( /// This function will just pass the expression as is in most cases. pub(crate) fn translate_expr(expr: &syn::Expr) -> String { match expr { + syn::Expr::Index(i) => { + let base = translate_expr(&i.expr); + let index = translate_expr(&i.index); + format!("{base}[{index}]") + } + // This is done to deal with things like 3usize. + syn::Expr::Lit(l) => match &l.lit { + syn::Lit::Int(i) => { + let suffix = translate_primitive_type(i.suffix()); + let val = i.base10_digits().to_string(); + if suffix.is_empty() { + val + } else { + format!("({suffix}){val}") + } + } + _ => l.to_token_stream().to_string(), + }, syn::Expr::Path(p) => p.path.segments.last().unwrap().ident.to_string(), syn::Expr::Cast(c) => translate_expr(c.expr.deref()), expr => expr.to_token_stream().to_string(), diff --git a/libc-test/Cargo.toml b/libc-test/Cargo.toml index 66940ee8db169..68e10a62dd467 100644 --- a/libc-test/Cargo.toml +++ b/libc-test/Cargo.toml @@ -21,6 +21,7 @@ annotate-snippets = { version = "0.11.5", features = ["testing-colors"] } [build-dependencies] cc = "1.2.29" ctest = { path = "../ctest" } +ctest-next = { path = "../ctest-next" } regex = "1.11.1" [features] @@ -33,6 +34,11 @@ name = "ctest" path = "test/ctest.rs" harness = false +[[test]] +name = "ctest_next" +path = "test/ctest_next.rs" +harness = false + [[test]] name = "linux-fcntl" path = "test/linux_fcntl.rs" diff --git a/libc-test/build.rs b/libc-test/build.rs index b15ad1da76926..ac7ffa44362bf 100644 --- a/libc-test/build.rs +++ b/libc-test/build.rs @@ -77,6 +77,11 @@ fn ctest_cfg() -> ctest::TestGenerator { ctest::TestGenerator::new() } +#[expect(unused)] +fn ctest_next_cfg() -> ctest_next::TestGenerator { + ctest_next::TestGenerator::new() +} + fn do_semver() { let mut out = PathBuf::from(env::var("OUT_DIR").unwrap()); out.push("semver.rs"); @@ -164,6 +169,14 @@ fn main() { let re = regex::bytes::Regex::new(r"(?-u:\b)crate::").unwrap(); copy_dir_hotfix(Path::new("../src"), &hotfix_dir, &re, b"::"); + // FIXME(ctest): Only needed until ctest-next supports all tests. + // Provide a default for targets that don't yet use `ctest-next`. + std::fs::write( + format!("{}/main_next.rs", std::env::var("OUT_DIR").unwrap()), + "\nfn main() { println!(\"test result: ok\"); }\n", + ) + .unwrap(); + do_cc(); do_ctest(); do_semver(); diff --git a/libc-test/test/ctest_next.rs b/libc-test/test/ctest_next.rs new file mode 100644 index 0000000000000..68ff7c619e635 --- /dev/null +++ b/libc-test/test/ctest_next.rs @@ -0,0 +1,5 @@ +#[allow(deprecated)] +#[allow(unused_imports)] +use libc::*; + +include!(concat!(env!("OUT_DIR"), "/main_next.rs"));